Al principio de esta lección, hemos explicado que los vectores (o puntos) se pueden escribir como matrices (una fila, tres columnas). No obstante, observa que también podríamos haberlos escrito como matrices (tres filas, una columna). Técnicamente, estas dos formas de expresar los puntos y vectores como matrices son perfectamente válidas y elegir un modo u otro es sólo una cuestión de convención.
Vector escrito como matriz: \( V=\begin{bmatrix}x & y & z\end{bmatrix})
Vector escrito como matriz:
En el primer ejemplo («matriz») hemos expresado nuestro vector o punto en lo que llamamos el orden fila-mayor: el vector (o punto) se escribe como una fila de tres números. En el segundo ejemplo, decimos que los puntos o vectores se escriben en orden columna-mayor: escribimos las tres coordenadas del vector o punto en vertical, como una columna.
Recuerda que expresamos puntos y vectores como matrices para multiplicarlos por matrices de transformación (por simplicidad trabajaremos con en lugar de matrices). También hemos aprendido que sólo podemos multiplicar matrices cuando el número de columnas de la matriz izquierda y el número de filas de la matriz derecha son iguales. En otras palabras, las matrices y pueden multiplicarse entre sí, pero las matrices y no. Nótese que si escribimos un vector como una matriz podemos multiplicarlo por una matriz (suponiendo que esta matriz está a la derecha dentro de la multiplicación), pero si escribimos este vector como una matriz entonces no podemos multiplicarlo por una matriz. Esto se ilustra en los siguientes ejemplos. Las dimensiones interiores (3 y 3) de las matrices implicadas en la multiplicación son las mismas (en verde) por lo que esta multiplicación es válida (y el resultado es un punto transformado escrito en forma de matriz):
$$* = \begin{bmatrix}x & y & z{end{bmatrix} * \begin{bmatrix}c_{00}&c_{01}&{c_{02}}\\c_{10}&c_{11}&{c_{12}}\\c_{20}&c_{21}&{c_{22}}\\\end{bmatrix} =\begin{bmatrix}x’&y’&z’\end{bmatrix}$$
The dimensiones interiores (1 y 3) de las matrices implicadas en la multiplicación no son iguales (en rojo) por lo que esta multiplicación no es posible:
$$* \N – flecha derecha \N – inicio{bmatriz}x\\N – y\N -z\N – fin{bmatriz} * c_{00}&c_{01}&{c_{02}} c_{10}&c_{11}&{c_{12}} c_{20}&c_{21}&{c_{22}}$$
Entonces, ¿qué hacemos? La solución a este problema no es multiplicar el vector o el punto por la matriz, sino la matriz M por el vector V. Es decir, movemos el punto o vector a la derecha dentro de la multiplicación:
$$* \N-rightarrow \begin{bmatrix} c_{00}&c_{01}&{c_{02}}\ c_{10}&c_{11}&{c_{12}} c_{20}&c_{21}&{c_{22}} \end{bmatrix}} * \begin{bmatrix}x\y\z{end{bmatrix} = \begin{bmatrix}x’\y’\z’\z{end{bmatrix}$$
Nota que el resultado de esta operación es un punto transformado escrito en forma de matriz. Así que tenemos un punto para empezar y terminamos con un punto transformado que es lo que queremos. Problema resuelto. Resumiendo, cuando por convención decidimos expresar vectores o puntos en orden fila-mayor (), tenemos que poner el punto a la izquierda de la multiplicación y el a la derecha dentro del signo de multiplicación. A esto se le llama en matemáticas, una multiplicación por la izquierda o pre-multiplicación. Si en cambio decides escribir los vectores en orden columna-mayor (), la matriz tiene que estar en el lado izquierdo de la multiplicación y el vector o punto en el lado derecho. A esto se le llama multiplicación a la derecha o postmultiplicación.
Hay que tener cuidado con el uso de estos términos. Por ejemplo la documentación de Maya dice que «las matrices son post-multiplicadas en Maya. Por ejemplo, para transformar un punto P del espacio-objeto al espacio-mundo (P’) habría que post-multiplicar por la WorldMatrix. (P’ = P x WM)», lo cual es confuso porque en realidad es una pre-multiplicación pero están hablando de la posición de la matriz con respecto al punto en este caso particular. En realidad es un uso incorrecto de la terminología. Debería haberse escrito que en maya los puntos y vectores se expresan como vectores fila-mayor y que, por tanto, se premultiplican (lo que significa que el punto o vector aparece antes que la matriz en la multiplicación).
La siguiente tabla resume las diferencias entre ambas convenciones (donde P, V y M significan respectivamente Punto, Vector y Matriz).
Orden mayor de las filas |
(P/V={comenzar{matriz}x & y & z{finalizar{matriz}\}) |
A la izquierda o premultiplicación |
P/V * M |
Columna-orden mayor |
(P/V=\año de inicio{matriz}x \año de finalización{matriz}) |
Multiplicación por la derecha o por la postmultiplicación |
M * P/V |
Ahora que hemos conocido estas dos convenciones te preguntarás «¿no se trata sólo de escribir cosas en papel?». Sabemos cómo calcular el producto de dos matrices A y B: multiplicar cada coeficiente de la fila actual de A por los elementos asociados de la columna actual de B y sumar el resultado. Apliquemos esta fórmula utilizando las dos convenciones y comparemos los resultados:
Orden mayor-fila | |
---|---|
$${ \begin{bmatrix}x & y & zend{bmatrix} * \begin{bmatrix}a & b & c \d & e & f \g & h & i\bmatrix} }$$ |
$${ \begin{array}{l}x’ = x * a + y * d + z * g\y’ = x * b + y * e + z * h\z’ = x * c + y * f + z * i\end{array} }$$ |
Orden mayor | |
$${ \begin{bmatrix} a & b & c \d & e & f \g & h & i \end{bmatrix} * \begin{bmatrix}x\\y\\z\end{bmatrix} }$$ |
$${begin{array}{l}x’ = a * x + b * y + c * z\\y’ = d * x + e * y + f * z\z’ = g * x + h * y + i * z\end{array} }$$ |
Multiplicar un punto o un vector por una matriz debería darnos el mismo resultado tanto si utilizamos el orden de fila como el de columna. Si utilizas una aplicación 3D para rotar un punto en un determinado ángulo alrededor del eje z, esperas que el punto esté en una determinada posición después de la rotación, independientemente de la convención interna que el desarrollador haya utilizado para representar puntos y vectores. Sin embargo, como puedes ver en la tabla anterior, multiplicar un punto (o vector) fila-mayor y columna-mayor por la misma matriz claramente no nos daría el mismo resultado. Para ponernos en situación, en realidad tendríamos que transponer la matriz utilizada en la multiplicación columna-mayor para asegurarnos de que x’, y’ y z’ son iguales (si necesitas recordar qué es la transposición de una matriz, consulta el capítulo de Operaciones Matriciales). Esto es lo que obtenemos:
Orden mayor de las filas | |
---|---|
$${ \begin{bmatrix}x & y & z\end{bmatrix} * \begin{bmatrix}a & b & c \d & e & f \g & h & i\bmatrix} }$$ |
$${begin{array}{l}x’ = x * a + y * d + z * g\y’ = x * b + y * e + z * h\z’ = x * c + y * f + z * i\end{array} }$$ |
Orden mayor | |
$${ \begin{bmatrix} a & d & g \b & e & h \c & f & i{end{bmatrix} * \begin{bmatrix}x\\y\\z\end{bmatrix} }$$ |
$${begin{array}{l}x’ = a * x + d * y + g * z\\y’ = b * x + e * y + h * z\z’ = c * x + f * y + i * z\end{array} }$$ |
En conclusión, pasar del orden fila-mayor al orden columna-mayor no sólo implica intercambiar el punto o vector y la matriz en la multiplicación sino también transponer la matriz, para garantizar que ambas convenciones den el mismo resultado (y viceversa).
A partir de estas observaciones, podemos ver que cualquier serie de transformaciones aplicadas a un punto o a un vector cuando se utiliza una convención fila-mayor puede escribirse en orden secuencial (u orden de lectura). Imagina, por ejemplo, que quieres trasladar el punto P con la matriz T y luego rotarlo alrededor del eje z con Rz y luego alrededor del eje y con Ry. Puedes escribir:
$$P’=P * T * R_z * R_y$$
Si utilizaras una notación columna-mayor tendrías que llamar a la transformación en orden inverso (lo que podría resultar contraintuitivo):
$$P’=R_y * R_z * T * P$$
Así que puedes pensar, «debe haber una razón para preferir un sistema a otro». En realidad, ambas convenciones son correctas y nos dan el mismo resultado, pero por algunas razones técnicas, los textos de Matemáticas y Física suelen tratar los vectores como vectores columna.
El orden de transformación cuando usamos matrices columna-mayor es más similar en matemáticas a la forma en que escribimos la evaluación y composición de funciones.
La convención de matrices fila-mayor sin embargo hace que las matrices sean más fáciles de enseñar, que es la razón por la que la usamos para Scratchapixel (así como Maya, DirectX. También se definen como estándar en las especificaciones de RenderMan). Sin embargo, algunas APIs 3D como OpenGL, utilizan una convención de columna mayor.
Implicación en la codificación: ¿Influye en el rendimiento?
Hay otro aspecto potencialmente muy importante a tener en cuenta si tienes que elegir entre fila-mayor y columna-mayor, pero esto no tiene nada que ver realmente con las convenciones en sí y lo práctico que es una sobre la otra. Tiene más que ver con el ordenador y su funcionamiento. Recuerda que vamos a tratar con matrices. Normalmente la implementación de una matriz en C++ tiene este aspecto:
Como puedes ver los 16 coeficientes de la matriz se almacenan en una matriz bidimensional de floats (o doubles dependiendo de la precisión que necesites. Nuestra clase Matriz de C++ es una plantilla). Lo que significa que en memoria los 16 coeficientes estarán dispuestos de la siguiente manera: c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33. En otras palabras, están dispuestos de forma contigua en la memoria. Ahora veamos cómo se accede a estos coeficientes en una multiplicación vectorial-matriz en la que los vectores se escriben en orden fila-mayor:
Como puedes ver los elementos de la matriz para x’ no se acceden secuencialmente. En otras palabras, para calcular x’ necesitamos el 1er, 5º y 9º flotante de la matriz 16 flotantes. Para calcular y’ necesitamos acceder al 2º, 6º y 10º float de esta matriz. Y finalmente para z’ necesitamos el 3º, 7º y 11º float de la matriz. En el mundo de la computación, acceder a los elementos de un array en un orden no secuencial, no es necesariamente algo bueno. De hecho, puede degradar el rendimiento de la caché de la CPU. No entraremos en demasiados detalles aquí, pero digamos que la memoria más cercana a la que la CPU puede acceder se llama caché. Esta caché es de acceso muy rápido pero sólo puede almacenar un número muy limitado de datos. Cuando la CPU necesita acceder a algún dato, primero comprueba si existe en la caché. Si existe, la CPU accede a estos datos inmediatamente (cache hit), pero si no existe (cache miss), primero tiene que crear una entrada en la caché para ello, y luego copiar a esta ubicación los datos de la memoria principal. Este proceso consume obviamente más tiempo que cuando los datos ya existen en la caché, por lo que lo ideal es evitar los fallos de caché en la medida de lo posible. Además de copiar los datos particulares de la memoria principal, la CPU también copia un trozo de los datos que viven justo al lado (por ejemplo, los siguientes 24 bytes), porque los ingenieros de hardware pensaron que si tu código necesitaba acceder a un elemento de un array, por ejemplo, era probable que accediera a los elementos que le siguen poco después. De hecho, en los programas, a menudo hacemos un bucle sobre los elementos de una matriz en orden secuencial y, por tanto, es probable que esta suposición sea cierta. Aplicado a nuestro problema matricial, acceder a los coeficientes de la matriz en orden no secuencial puede ser, por tanto, un problema. Asumiendo que la CPU carga el flotador solicitado en la caché más los 3 flotadores que le siguen, nuestra implementación actual podría llevar a muchos fallos de caché, ya que los coeficientes utilizados para calcular x’ y’ y z’ están separados por 5 flotadores en la matriz. Por otro lado, si se utiliza una notación de orden columna-mayor, el cálculo de x’, por ejemplo, requiere acceder a los elementos 1º, 2º y 3º de la matriz.
Los coeficientes se acceden en orden secuencial lo que también significa que hacemos un buen uso del mecanismo de caché de la CPU (sólo 3 pérdidas de caché en lugar de 9 en nuestro ejemplo). En conclusión, podemos decir que, desde el punto de vista de la programación, implementar nuestra multiplicación de puntos o de matrices vectoriales utilizando una convención de orden colum-mayor podría ser mejor, en términos de rendimiento, que la versión que utiliza la convención de orden fila-mayor. En la práctica, sin embargo, no hemos sido capaces de demostrar que este sea el caso (cuando se compila el programa utilizando las banderas de optimización -O, -O2 o -O3, el compilador puede hacer el trabajo por usted mediante la optimización de los bucles sobre arrays multidimensionales) y hemos estado utilizando con éxito la versión de orden fila-mayor sin ninguna pérdida de rendimiento en comparación con una versión del mismo código utilizando una implementación de orden columna-mayor.
Orden mayor de filas y mayor de columnas en computación
Para completar, mencionemos también que los términos orden mayor de filas y mayor de columnas también se pueden utilizar en computación para describir la forma en que los elementos de las matrices multidimensionales se disponen en la memoria. En el orden fila-mayor, los elementos de una matriz multidimensional se disponen uno tras otro, de izquierda a derecha, de arriba a abajo. Este es el método utilizado por C/C++. Por ejemplo, la matriz:
$$M = \begin{bmatrix}1&2&3\4&5&6\bend{bmatrix}$
se podría escribir en C/C++ como:
y los elementos de esta matriz se dispondrían contiguamente en la memoria lineal como:
En el orden columna-mayor, que utilizan lenguajes como FORTRAN y MATLAB, los elementos de la matriz se almacenan en memoria de arriba a abajo, de izquierda a derecha. Usando el mismo ejemplo de la matriz, los elementos de la matriz se almacenarían (y se accedería a ellos) en memoria de la siguiente manera:
Saber cómo se disponen los elementos de una matriz en memoria es importante sobre todo cuando se intenta acceder a ellos usando el desplazamiento de punteros y la optimización del bucle for (ya hemos explicado anteriormente en este capítulo que podría afectar al rendimiento de la caché de la CPU). Sin embargo, dado que sólo consideraremos C/C++ como nuestro lenguaje de programación, el ordenamiento columna-mayor (aplicado a la computación) no es de gran interés para nosotros. Sólo mencionamos lo que significan los términos en informática, para que sepas que pueden describir dos cosas diferentes según el contexto en el que se utilicen. Hay que tener cuidado de no confundirlos. En el contexto de las matemáticas, describen si tratas los vectores (o puntos) como filas de coordenadas o como columnas y el segundo, y en el contexto de la informática, describen la forma en que un determinado lenguaje de programación almacena y accede a los elementos de una matriz multidimensional (que son las matrices) en la memoria.
OpenGL es un caso interesante en ese sentido. Cuando se creó inicialmente GL, los desarrolladores eligieron la convención del vector fila-mayor. Sin embargo, los desarrolladores que ampliaron OpenGL pensaron que debían volver al vector columna-mayor, y así lo hicieron. Sin embargo, por razones de compatibilidad, no quisieron cambiar el código de la multiplicación punto-matriz y decidieron, en cambio, cambiar el orden de almacenamiento de los coeficientes de la matriz en la memoria. En otras palabras, OpenGL almacena los coeficientes en orden columna-mayor, lo que significa que los coeficientes de traslación m03, m13 y m23 de una matriz que utiliza un vector columna-mayor tienen los índices 13, 14, 15 en la matriz de flotadores, al igual que los coeficientes de traslación m30, m31 y m32 de una matriz que utiliza un vector fila-mayor.
Resumen
Las diferencias entre ambas convenciones se resumen en la siguiente tabla:
Vector mayor fila (Matemáticas) | Vector mayor columna-vector mayor (Matemáticas) |
---|---|
(P/V=\nin{bmatriz}x & y & z\nfinal{bmatriz}) |
(P/V=\año{matriz}x \año{y} z\año{matriz}) |
Pre-multiplicación \(vM\) |
Posmultiplicación \(Mv\) |
El orden de llamada y el orden de aplicación de las transformaciones es el mismo: «toma P, transforma por T, transforma por Rz, transforma por Ry» se escribe como \(P’=P*T*R_z*R_y\) |
El orden de llamada es el inverso al orden en que se aplican las transformaciones: «tomar P, transformar por T, transformar por Rz, transformar por Ry» se escribe como \(P’=R_y*R_z*T*P\) |
API: Direct X, Maya |
API: OpenGL, PBRT, Blender |
Las filas de la matriz representan las bases (o ejes) de un sistema de coordenadas (rojo: eje x, verde: eje y, azul:eje z) $${begin{bmatrix} \color{red}{c_{00}}& \color{red}{c_{01}}&\color{red}{c_{02}}&0\\ \color{green}{c_{10}}& \color{green}{c_{11}}&\color{green}{c_{12}}&0\\ \color{blue}{c_{20}}& \color{blue}{c_{21}}&\color{blue}{c_{22}}&0\\0&0&0&1 \end{bmatrix} } $$ |
Las columnas de la matriz representan las bases (o ejes) de un sistema de coordenadas (rojo: eje x, verde: eje y, azul:eje z) $${{begin{bmatrix} \color{red}{c_{00}}& \color{green}{c_{01}}&\color{blue}{c_{02}}&0\\ \color{red}{c_{10}}& \color{green}{c_{11}}&\color{blue}{c_{12}}&0\\ \color{red}{c_{20}}& \color{green}{c_{21}}&\color{blue}{c_{22}}&0\\0&0&0&1\end{bmatrix} }$$ |
Los valores de traducción se almacenan en los elementos c30, c31 y c32. $${\begin{bmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\Tx&Ty&Tz&1\end{bmatrix} }$$ |
Los valores de traducción se almacenan en los elementos c03, c13 y c23. $${begin{bmatrix}1&0&0&Tx{0&1&0&Ty{0&0&1&Tz{0&0&1end{bmatrix} }$$ |
Transponer la matriz para usarla como una matriz ordenada de columna-mayor |
Transponer la matriz para usarla como una matriz ordenada de fila-mayormatriz ordenada mayor |
Matriz mayor-fila (Informática) | Matriz mayor-columna (Informática) |
API: Direct X, Maya, PBRT |
API: OpenGL |
Un lector publicó una pregunta en Stackoverflow sugiriendo que la tabla anterior era confusa. El tema es confuso y, a pesar de nuestro mejor intento de arrojar algo de luz sobre el asunto, mucha gente sigue confundiéndose al respecto. Pensamos que nuestra respuesta en Stackoverflow podría, con suerte, aportar otra visión sobre la cuestión.
Tienes la teoría (lo que haces en matemáticas con un lápiz y un papel) y lo que haces con tu implementación (C++). Son dos problemas diferentes.
Matemáticas: puedes usar dos notaciones, o bien columna o bien fila mayor. Con vector mayor de fila, en papel, necesitas escribir la multiplicación vector-matriz vM donde v es el vector de fila (1×4) y M su matriz 4×4. ¿Por qué? Porque matemáticamente sólo se puede escribir *, y no al revés. Del mismo modo, si utilizas la columna, entonces el vector debe escribirse en vertical, o en notación (4 filas, 1 columna). Así, la multiplicación con una matriz sólo puede escribirse así . Nótese que la matriz se pone delante del vector: Mv. La primera notación se llama multiplicación por la izquierda o pre-multiplicación (porque el vector está en el lado izquierdo del producto) y la segunda (Mv) se llama multiplicación por la derecha o post-multiplicación (porque el vector está en el lado derecho del producto). Como ves los términos derivan de si el vector está en el lado izquierdo (delante de, o «pre») o en el lado derecho (después de, o «post») de la matriz.
Ahora, si necesitas transformar un vector (o un punto) entonces tienes que prestar atención al orden de la multiplicación, cuando los escribas en el papel. Si quieres trasladar algo con la matriz T y luego rotar con R y luego escalar con S, entonces en un mundo de columna mayor, necesitas escribir v’ = S * R * T * v. En un mundo de fila mayor necesitas escribir v’ = v * T * R * S.
Eso es para la teoría. Llamemos a eso la convención de vectores fila/columna.
Ordenador: luego viene el momento en que decides implementar esto en C++ digamos. Lo bueno de esto es que C++ no te impone nada de nada. Puedes mapear los valores de los coeficientes de tu matriz en memoria como quieras, y puedes escribir el código para realizar una multiplicación matricial por otra matriz como quieras. Del mismo modo, la forma de acceder a los coeficientes para una multiplicación vectorial-matriz depende completamente de ti. Debes distinguir claramente entre cómo asignas tus coeficientes en la memoria y qué convenciones necesitas utilizar desde un punto de vista matemático para representar tus vectores. Se trata de dos problemas independientes. Llamemos a esta parte la disposición fila/columna-mayor.
Por ejemplo puedes declarar una clase matriz como un array de digamos 16 flotantes contiguos. Eso está bien. Donde los coeficientes m14, m24, m34 representan la parte de traslación de la matriz (Tx, Ty, Tz), por lo que asumes que tu «convención» es fila-mayor aunque te digan que uses la convención de matrices de OpenGL que se dice que es columna-mayor. Aquí la posible confusión viene del hecho de que el mapeo de los coeficientes en la memoria es diferente de la representación mental que estás haciendo de una matriz «columna-mayor». Tú codificas «fila» pero se dice que usas (desde un punto de vista matemático) «columna», de ahí tu dificultad para dar sentido a si haces las cosas bien o mal.
Lo importante es ver una matriz como una representación de un sistema de coordenadas definido por tres ejes, y una traslación. Dónde y cómo se almacenan estos datos en la memoria depende completamente de ti. Suponiendo que los tres vectores que representan los tres ejes del sistema de coordenadas se denominan AX(x,y,z), AY(x,y,z), AZ(x,y,z), y el vector de traslación se denota por (Tx, Ty, Tz), entonces matemáticamente si se utiliza el vector columna se tiene:
$$M = \begin{bmatrix} AXx & AYx & AZx & Tx\\\N- AXy & AYy & AZy & Ty \N- AXz & AYz & AZz & Tz \N- 0 & 0 & 1 & 1\N-end{bmatrix}$
Los ejes del sistema de coordenadas se escriben en vertical. Ahora bien, si usted tiene si se utiliza fila-mayor:
$$M = \begin{bmatrix} AXx & AXy & AXz & 0\\N- AYx & AYy & AYz & 0 \N- AZx & AZy & AZz & 0 \N- Tx & Ty & Tz & 1end{bmatrix}$
Los ejes del sistema de coordenadas se escriben horizontalmente. Así que el problema ahora cuando se trata de mundo de la informática, es cómo su almacenar estos coeficientes en la memoria. También puedes hacer:
¿Te dice sin embargo qué convención utilizas? No. También puedes escribir:
o:
De nuevo, eso no le da una indicación particular de qué convención «matemática» utiliza. Simplemente estás almacenando 16 coeficientes en la memoria de diferentes maneras y eso está perfectamente bien mientras sepas cuál es esa manera, para que puedas acceder a ellos apropiadamente más adelante. Ahora ten en cuenta que un vector multiplicado por una matriz debería darte el mismo vector tanto si usas una notación matemática de filas como de columnas. Por lo tanto, lo que realmente es importante es que multiplique las coordenadas (x,y,z) de su vector por los coeficientes correctos de la matriz, lo que requiere el conocimiento de cómo «usted» ha decidido almacenar el coeficiente de la matriz en la memoria:
Escribimos esta función para subrayar el hecho de que, independientemente de la convención que utilices, el resultado de la multiplicación de vectores * matrices es sólo una multiplicación y una suma entre las coordenadas de entrada del vector y las coordenadas de los ejes del sistema de coordenadas AX, AY y AZ (independientemente de la notación que utilices, e independientemente de la forma en que las almacenes en la memoria). Si utilizas:
Tienes que llamar a:
Si usas:
Necesitas llamar:
¿Eso te dice qué convención utilizas? No. Sólo necesitas llamar a los coeficientes correctos en los lugares correctos cuando haces una multiplicación vec * mat. Y eso es todo, por desconcertante que parezca.Ahora las cosas son ligeramente diferentes cuando se trata de la multiplicación mat * mat. Se puede asumir que el orden en el que se multiplican las matrices no es el mismo. Así que R * S * T no es lo mismo que T * S * R. El orden sí importa. Ahora, de nuevo, si se utiliza «fila mayor», entonces matemáticamente hay que escribir:
donde ml es la matriz de la izquierda y mr la de la derecha: mt = ml * mr. Sin embargo, tenga en cuenta que no hemos estado utilizando paréntesis para los índices de acceso porque no queremos sugerir que estamos accediendo a elementos almacenados en una matriz 1D aquí. Sólo estamos hablando de los coeficientes de las matrices tal y como se escriben en papel. Si quieres escribir esto en C++, entonces todo depende de cómo hayas almacenado tus coeficientes en memoria como se sugirió anteriormente.