Articulo de referencia

Programación de matrices

En informática , la programación de matrices se refiere a soluciones que permiten aplicar operaciones a un conjunto completo de valores a la vez. Estas soluciones se utilizan co...

En informática , la programación de matrices se refiere a soluciones que permiten aplicar operaciones a un conjunto completo de valores a la vez. Estas soluciones se utilizan comúnmente en entornos científicos y de ingeniería.

Los lenguajes de programación modernos que admiten programación de matrices (también conocidos como lenguajes vectoriales o multidimensionales ) se han diseñado específicamente para generalizar las operaciones en escalares y aplicarlas de forma transparente a vectores , matrices y arreglos de dimensiones superiores. Estos incluyen APL , J , Fortran , MATLAB , Analytica , Octave , PL/I , R , Cilk Plus , Julia , Perl Data Language (PDL) y Raku . En estos lenguajes, una operación que opera sobre matrices completas puede denominarse operación vectorizada , [ 1 ] independientemente de si se ejecuta en un procesador vectorial , que implementa instrucciones vectoriales. Las primitivas de programación de matrices expresan de forma concisa ideas generales sobre la manipulación de datos. El nivel de concisión puede ser drástico en ciertos casos: no es raro encontrar líneas de código de lenguajes de programación de matrices que requieren varias páginas de código orientado a objetos.

Conceptos de matriz

La idea fundamental de la programación con matrices es que las operaciones se aplican simultáneamente a un conjunto completo de valores. Esto la convierte en un modelo de programación de alto nivel, ya que permite al programador pensar y operar con conjuntos de datos completos, sin tener que recurrir a bucles explícitos de operaciones escalares individuales.

Kenneth E. Iverson describió la lógica detrás de la programación de matrices (en realidad refiriéndose a APL) de la siguiente manera: [ 2 ]

La mayoría de los lenguajes de programación son decididamente inferiores a la notación matemática y se utilizan poco como herramientas de pensamiento de maneras que serían consideradas significativas por, por ejemplo, un matemático aplicado.

La tesis plantea que las ventajas de ejecutabilidad y universalidad propias de los lenguajes de programación pueden combinarse eficazmente, en un único lenguaje coherente, con las ventajas que ofrece la notación matemática. Es importante distinguir la dificultad de describir y aprender una notación de la dificultad de dominar sus implicaciones. Por ejemplo, aprender las reglas para calcular un producto matricial es sencillo, pero dominar sus implicaciones (como su asociatividad, su distributividad respecto de la suma y su capacidad para representar funciones lineales y operaciones geométricas) es un asunto distinto y mucho más complejo.

De hecho, el carácter sugerente de una notación puede hacer que parezca más difícil de aprender debido a las numerosas propiedades que sugiere para su exploración.

[...]

Los usuarios de ordenadores y lenguajes de programación suelen preocuparse principalmente por la eficiencia de ejecución de los algoritmos y, por lo tanto, podrían descartar sin más muchos de los algoritmos aquí presentados. Tal descarte sería un error, ya que una descripción clara de un algoritmo suele servir de base para derivar fácilmente un algoritmo más eficiente.

La base de la programación y el pensamiento basados ​​en matrices consiste en encontrar y aprovechar las propiedades de los datos donde los elementos individuales son similares o adyacentes. A diferencia de la programación orientada a objetos, que descompone implícitamente los datos en sus partes constituyentes (o cantidades escalares ), la programación orientada a matrices busca agrupar los datos y aplicar un tratamiento uniforme.

El rango de las funciones es un concepto importante en los lenguajes de programación de matrices en general, por analogía con el rango de los tensores en matemáticas: las funciones que operan sobre datos se pueden clasificar según el número de dimensiones sobre las que actúan. La multiplicación ordinaria, por ejemplo, es una función de rango escalar porque opera sobre datos de dimensión cero (números individuales). El producto vectorial es un ejemplo de función de rango vectorial porque opera sobre vectores, no sobre escalares. La multiplicación de matrices es un ejemplo de función de rango 2, porque opera sobre objetos bidimensionales (matrices). Los operadores de colapso reducen la dimensionalidad de una matriz de datos de entrada en una o más dimensiones. Por ejemplo, la suma sobre los elementos colapsa la matriz de entrada en una dimensión.

Usos

La programación de matrices se adapta muy bien a la paralelización implícita , un tema de mucha investigación en la actualidad. Además, las CPU de Intel y compatibles desarrolladas y producidas después de 1997 contenían varias extensiones de conjuntos de instrucciones, desde MMX hasta SSSE3 y 3DNow!, que incluyen capacidades rudimentarias de matrices SIMD . Esto continuó en la década de 2020 con conjuntos de instrucciones como AVX-512 , convirtiendo a las CPU modernas en sofisticados procesadores vectoriales. El procesamiento de matrices se distingue del procesamiento paralelo en que un procesador físico realiza operaciones en un grupo de elementos simultáneamente, mientras que el procesamiento paralelo busca dividir un problema mayor en problemas más pequeños ( MIMD ) para ser resueltos por partes por numerosos procesadores. Los procesadores con múltiples núcleos y las GPU con miles de núcleos de computación general son comunes desde 2023.

Idiomas

Los ejemplos canónicos de lenguajes de programación de matrices son Fortran , APL y J. Otros incluyen: A+ , Analytica , Chapel , IDL , Julia , K , Klong, Q , MATLAB , GNU Octave , Scilab , FreeMat , Perl Data Language (PDL), R , Raku , S-Lang , SAC , Nial , ZPL , Futhark y TI-BASIC .

Lenguajes escalares

En lenguajes escalares como C y Pascal , las operaciones se aplican solo a valores individuales, por lo que a + b representa la suma de dos números. En estos lenguajes, sumar un array a otro requiere indexación y bucles, cuya codificación resulta tediosa.

for ( int i = 0 ; i < m ; i ++ ) { for ( int j = 0 ; j < n ; j ++ ) { a [ i ][ j ] += b [ i ][ j ]; } }

En lenguajes basados ​​en matrices, por ejemplo en Fortran, el bucle for anidado anterior se puede escribir en formato de matriz en una sola línea,

a = a + b

o alternativamente, para enfatizar la naturaleza de matriz de los objetos,

a (:,:) = a (:,:) + b (:,:)

Si bien los lenguajes escalares como C no cuentan con elementos de programación de matrices nativos, esto no significa que los programas escritos en estos lenguajes nunca aprovechen las técnicas subyacentes de vectorización (es decir, utilizando las instrucciones vectoriales de la CPU , si las tiene, o empleando múltiples núcleos). Algunos compiladores de C, como GCC, detectan y vectorizan, en ciertos niveles de optimización, secciones de código que, según sus heurísticas, se beneficiarían de ello. Otra alternativa la proporciona la API OpenMP , que permite paralelizar secciones de código aplicables aprovechando los múltiples núcleos de la CPU.

lenguajes de matrices

En los lenguajes de matrices, las operaciones se generalizan para aplicarse tanto a escalares como a matrices. Así, a + b expresa la suma de dos escalares si a y b son escalares, o la suma de dos matrices si son matrices.

Un lenguaje de matrices simplifica la programación, pero posiblemente a un costo conocido como penalización por abstracción . [ 3 ] [ 4 ] [ 5 ] Debido a que las sumas se realizan de forma aislada del resto del código, es posible que no produzcan el código más eficiente . (Por ejemplo, las sumas de otros elementos de la misma matriz pueden encontrarse posteriormente durante la misma ejecución, lo que provoca búsquedas repetidas innecesarias). Incluso el compilador optimizador más sofisticado tendría muchísimas dificultades para fusionar dos o más funciones aparentemente dispares que podrían aparecer en diferentes secciones o subrutinas del programa, aunque un programador podría hacerlo fácilmente, agregando sumas en la misma pasada sobre la matriz para minimizar la sobrecarga .

Ada

El código C anterior se convertiría en el siguiente en el lenguaje Ada , [ 6 ] que admite la sintaxis de programación de matrices.

A := A + B ;

APL

APL utiliza símbolos Unicode de un solo carácter sin azúcar sintáctico.

A A + B

Esta operación funciona en matrices de cualquier rango (incluido el rango 0), y en un escalar y una matriz. Dyalog APL extiende el lenguaje original con asignaciones aumentadas :

A + B

Analítica

Analytica ofrece la misma economía de expresión que Ada.

A := A + B; 

BÁSICO

Dartmouth BASIC incluía instrucciones MAT para la manipulación de matrices y arreglos en su tercera edición (1966).

DIM A ( 4 ), B ( 4 ), C ( 4 ) MAT A = 1 MAT B = 2 * A MAT C = A + B MAT PRINT A , B , C

Mata

El lenguaje de programación matricial Mata de Stata admite la programación de matrices. A continuación, ilustramos la suma, la multiplicación, la suma de una matriz y un escalar, la multiplicación elemento a elemento, la indexación y una de las muchas funciones de matriz inversa de Mata.

. mata : : A = ( 1 , 2 , 3 ) \( 4 , 5 , 6 ) : A 1 2 3 +-------------+ 1 | 1 2 3 | 2 | 4 5 6 | +-------------+ : B = ( 2 .. 4 ) \( 1 .. 3 ) : B 1 2 3 +-------------+ 1 | 2 3 4 | 2 | 1 2 3 | +-------------+ : C = J ( 3 , 2 , 1 ) // Una matriz de unos de 3 por 2 : C 1 2 +---------+ 1 | 1 1 | 2 | 1 1 | 3 | 1 1 | +---------+ : D = A + B : D 1 2 3 +-------------+ 1 | 3 5 7 | 2 | 5 7 9 | +-------------+ : E = A * C : E 1 2 +-----------+ 1 | 6 6 | 2 | 15 15 | +-----------+ : F = A: * B : F 1 2 3 +++ 1 | 2 6 12 | 2 | 4 10 18 | +++ : G = E : + 3 : G 1 2 +-----------+ 1 | 9 9 | 2 | 18 18 | +-----------+ : H = F[( 2 \ 1 ), ( 1 , 2 )] // Subíndices para obtener una submatriz de F y : // intercambiar la fila 1 y la fila 2 : H 1 2 +-----------+ 1 | 4 10 | 2 | 2 6 | +-----------+ : I = invsym (F' * F) // Inversa generalizada (F*F^(-1)F=F) de una : // matriz simétrica semidefinida positiva : I [simétrico] 1 2 3 +-------------------------------------------+ 1 | 0 | 2 | 0 3.25 | 3 | 0 - 1.75 . 9444444444 | +-------------------------------------------+ : fin

MATLAB

La implementación en MATLAB permite la misma economía que permite el uso del lenguaje Fortran.

A = A + B ;

Una variante del lenguaje MATLAB es el lenguaje GNU Octave , que extiende el lenguaje original con asignaciones aumentadas:

A += B ;

Tanto MATLAB como GNU Octave admiten de forma nativa operaciones de álgebra lineal como la multiplicación de matrices, la inversión de matrices y la solución numérica de sistemas de ecuaciones lineales , incluso utilizando la pseudoinversa de Moore-Penrose . [ 7 ] [ 8 ]

El ejemplo de Nial del producto interno de dos arreglos se puede implementar utilizando el operador de multiplicación de matrices nativo. Si aes un vector fila de tamaño [1 n] y bes un vector columna correspondiente de tamaño [n 1].

a * b;

Por el contrario, el producto por entrada se implementa de la siguiente manera:

a .* b;

El producto interno entre dos matrices que tienen el mismo número de elementos se puede implementar con el operador auxiliar (:), que transforma una matriz dada en un vector columna, y el operador de transposición' :

A(:)' * B(:);

rasql

El lenguaje de consulta rasdaman es un lenguaje de programación de matrices orientado a bases de datos. Por ejemplo, se podrían sumar dos matrices con la siguiente consulta:

SELECCIONAR A + B DE A , B

R

El lenguaje R admite el paradigma de matrices de forma predeterminada. El siguiente ejemplo ilustra un proceso de multiplicación de dos matrices seguido de la suma de un escalar (que, de hecho, es un vector de un elemento) y un vector:

> A <- matrix ( 1 : 6 , nrow = 2 ) # !!esto tiene nrow=2 ... y A tiene 2 filas > A  [,1] [,2] [,3] [1,] 1 3 5 [2,] 2 4 6 > B <- t ( matrix ( 6 : 1 , nrow = 2 ) ) # t() es un operador de transposición !!esto tiene nrow=2 ... y B tiene 3 filas --- una clara contradicción con la definición de A > B  [,1] [,2] [1,] 6 5 [2,] 4 3 [3,] 2 1 > C <- A %*% B > C  [,1] [,2] [1,] 28 19 [2,] 40 28 > D <- C + 1 > D  [,1] [,2] [ 1,] 29 20 [2,] 41 29 > D + c ( 1 , 1 ) # c() crea un vector  [,1] [,2] [1,] 30 21 [2,] 42 30

Raku

Raku admite el paradigma de matrices a través de sus metaoperadores. [ 9 ] El siguiente ejemplo demuestra la suma de las matrices @a y @b utilizando el hiperoperador junto con el operador de suma.

[ 0 ] > mi @a = [[ 1 , 1 ],[ 2 , 2 ],[ 3 , 3 ]]; [[ 1 1 ] [ 2 2 ] [ 3 3 ]] [ 1 ] > mi @b = [[ 4 , 4 ],[ 5 , 5 ],[ 6 , 6 ]]; [[ 4 4 ] [ 5 5 ] [ 6 6 ]] [ 2 ] > @a »+« @b ; [[ 5 5 ] [ 7 7 ] [ 9 9 ]] 

Razonamiento matemático y notación del lenguaje

El operador de división izquierda de matrices expresa concisamente algunas propiedades semánticas de las matrices. Al igual que en su equivalente escalar, si el determinante del coeficiente (matriz) Ano es nulo, entonces es posible resolver la ecuación (vectorial) A * x = bmultiplicando ambos lados por la izquierda por el inverso de A: (tanto en MATLAB como en GNU Octave: ). Las siguientes afirmaciones matemáticas son válidas cuando es una matriz cuadrada de rango completo :A−1A^-1A

A^-1 *(A * x)==A^-1 * (b)
(A^-1 * A)* x ==A^-1 * b    ( asociatividad de la multiplicación de matrices )
x = A^-1 * b

donde ==es el operador relacional de equivalencia . Las afirmaciones anteriores también son expresiones válidas de MATLAB si la tercera se ejecuta antes que las demás (las comparaciones numéricas pueden ser erróneas debido a errores de redondeo).

Si el sistema está sobredeterminado, es decir, Atiene más filas que columnas, la pseudoinversa (en los lenguajes MATLAB y GNU Octave: ) puede reemplazar a la inversa , como sigue:A+pinv(A)A−1

pinv(A)*(A*x)==pinv(A)*(b)
(pinv(A)*A)*x==pinv(A)*b   (asociatividad de la multiplicación de matrices)
x=pinv(A)*b

Sin embargo, estas soluciones no son ni las más concisas (por ejemplo, aún persiste la necesidad de diferenciar notacionalmente los sistemas sobredeterminados) ni las más eficientes computacionalmente. Este último punto es fácil de comprender al considerar nuevamente el equivalente escalar a * x = b, para el cual la solución x = a^-1 * brequeriría dos operaciones en lugar de la más eficiente x = b / a. El problema es que, en general, las multiplicaciones de matrices no son conmutativas , ya que la extensión de la solución escalar al caso matricial requeriría:

(a * x)/ a ==b / a
(x * a)/ a ==b / a   (¡La conmutatividad no se cumple para las matrices!)
x * (a / a)==b / a   (La asociatividad también se cumple para las matrices)
x = b / a

El lenguaje MATLAB introduce el operador de división izquierda \para mantener la parte esencial de la analogía con el caso escalar, simplificando así el razonamiento matemático y preservando la concisión:

A \ (A * x)==A \ b
(A \ A)* x ==A \ b   (La asociatividad también se cumple para las matrices, por lo que ya no se requiere la conmutatividad).
x = A \ b

Esto no es solo un ejemplo de programación de matrices concisa desde el punto de vista de la codificación, sino también desde la perspectiva de la eficiencia computacional, que en varios lenguajes de programación de matrices se beneficia de bibliotecas de álgebra lineal bastante eficientes como ATLAS o LAPACK . [ 10 ]

Volviendo a la cita anterior de Iverson, la razón que la sustenta debería ser ahora evidente:

Es importante distinguir la dificultad de describir y aprender una notación de la dificultad de dominar sus implicaciones. Por ejemplo, aprender las reglas para calcular un producto matricial es fácil, pero dominar sus implicaciones (como su asociatividad, su distributividad respecto a la suma y su capacidad para representar funciones lineales y operaciones geométricas) es algo distinto y mucho más difícil. De hecho, la propia naturaleza sugerente de una notación puede hacer que parezca más difícil de aprender debido a las numerosas propiedades que sugiere para su exploración.

Bibliotecas de terceros

El uso de bibliotecas especializadas y eficientes para proporcionar abstracciones más concisas también es común en otros lenguajes de programación. En C++, varias bibliotecas de álgebra lineal aprovechan la capacidad del lenguaje para sobrecargar operadores . En algunos casos, una abstracción muy concisa en esos lenguajes está influenciada explícitamente por el paradigma de programación de matrices, como lo hacen la biblioteca de extensión NumPy para Python , Armadillo y las bibliotecas Blitz++ . [ 11 ] [ 12 ]

Véase también

Referencias

  1. Stéfan van der Walt; S. Chris Colbert y Gaël Varoquaux (2011). "El array NumPy: una estructura para la computación numérica eficiente". Computing in Science and Engineering . 13 (2). IEEE: 22– 30. arXiv : 1102.1523 . Bibcode : 2011CSE....13b..22V . doi : 10.1109/mcse.2011.37 . S2CID 16907816 . 
  2. Iverson, KE (1980). "La notación como herramienta del pensamiento" . Communications of the ACM . 23 (8): 444– 465. doi : 10.1145/358896.358899 .
  3. Surana P (2006). Meta-compilación de abstracciones del lenguaje (Tesis).
  4. Kuketayev. "El punto de referencia de penalización por abstracción de datos (DAP) para objetos pequeños en Java" . Archivado del original el 11 de enero de 2009. Consultado el 17 de marzo de 2008 .
  5. Chatzigeorgiou; Stephanides (2002). "Evaluación del rendimiento y la potencia de los lenguajes de programación orientados a objetos frente a los procedimentales". En Blieberger; Strohmeier (eds.). Actas de la 7.ª Conferencia Internacional sobre Tecnologías de Software Confiables - Ada-Europe'2002 . Springer. pág. 367. ISBN  978-3-540-43784-0.
  6. Manual de referencia de Ada : G.3.1 Vectores y matrices reales
  7. "Manual de GNU Octave. Operadores aritméticos" . Consultado el 19 de marzo de 2011 .
  8. "Documentación de MATLAB. Operadores aritméticos" . Archivado del original el 7 de septiembre de 2010. Consultado el 19 de marzo de 2011 .
  9. "Sección de metaoperadores de la documentación del operador Raku" .
  10. "Manual de GNU Octave. Apéndice G Instalación de Octave" . Consultado el 19 de marzo de 2011 .
  11. "Referencia para Armadillo 1.1.8. Ejemplos de sintaxis de Matlab/Octave y sintaxis de Armadillo conceptualmente correspondiente" . Consultado el 19 de marzo de 2011 .
  12. "Guía del usuario de Blitz++. 3. Expresiones de matriz" . Archivado del original el 23 de marzo de 2011. Consultado el 19 de marzo de 2011 .
  • Programación "sin bucles apestosos"
  • Descubriendo lenguajes de matrices
  • Programación sobre "Tipos de arreglos"