Julia: Tomando en serio la transposición de vectores

Creado en 10 nov. 2013  ·  417Comentarios  ·  Fuente: JuliaLang/julia

de @alanedelman :

Realmente deberíamos pensar detenidamente cómo la transposición de un vector debería distribuir los diversos métodos A_*op*_B* . Debe ser posible evitar nuevos tipos y malas matemáticas. Por ejemplo, vectorvector que produce un vector (# 2472, # 2936), vector 'que produce una matriz y vector' 'que produce una matriz (# 2686) son malas matemáticas.

Lo que me funciona matemáticamente (lo que evita la introducción de un nuevo tipo) es que para un Vector v unidimensional:

  • v' es una operación (es decir, solo devuelve v ),
  • v'v o v'*v es un escalar,
  • v*v' es una matriz y
  • v'A o v'*A (donde A es un AbstractMatrix ) es un vector

Una transposición general _N_-dimensional invierte el orden de los índices. Un vector, que tiene un índice, debe ser invariante bajo transposición.

En la práctica, v' rara vez se usa de forma aislada y generalmente se encuentra en productos matriz-vector y matriz-matriz. Un ejemplo común sería construir formas bilineales v'A*w y formas cuadráticas v'A*v que se utilizan en gradientes conjugados, cocientes de Rayleigh, etc.

La única razón para introducir un nuevo tipo Transpose{Vector} sería representar la diferencia entre vectores contravariantes y covariantes, y no encuentro esto lo suficientemente convincente.

arrays breaking design linear algebra

Comentario más útil

BAM

Todos 417 comentarios

Por ejemplo, vectorvector que produce un vector (# 2472, # 2936), vector 'que produce una matriz y vector' 'que produce una matriz (# 2686) son malas matemáticas.

El doble-dual de un espacio vectorial de dimensión finita es isomorfo a él, no idéntico. Así que no tengo claro cómo son malas matemáticas. Es más que tendemos a pasar por alto la distinción entre cosas que son isomórficas en matemáticas, porque los cerebros humanos son buenos para manejar ese tipo de ambigüedad resbaladiza y simplemente hacer lo correcto. Dicho esto, estoy de acuerdo en que esto debería mejorarse, pero no porque sea matemáticamente incorrecto, sino porque es molesto.

¿Cómo puede v' == v , pero v'*v != v*v ? ¿Tiene más sentido del que pensamos que x' * y sea ​​su propio operador?

El doble-dual de un espacio vectorial de dimensión finita es isomorfo a él, no idéntico.

(Hablando como yo mismo ahora) No es solo isomorfo, es naturalmente isomorfo, es decir, el isomorfismo es independiente de la elección de la base. No puedo pensar en una aplicación práctica para la que valdría la pena distinguir entre este tipo de isomorfismo y una identidad. En mi opinión, el factor de molestia proviene de hacer este tipo de distinción.

¿Tiene más sentido del que pensamos que x' * y sea ​​su propio operador?

Esa fue la impresión que tuve de la discusión de esta tarde con @alanedelman.

Creo que lo que Jeff está pidiendo es justo en el dinero ... está empezando a parecer que x'_y y x_y 'está haciendo más
sentido que nunca.

Estoy de acuerdo violentamente con @stefan. Matemáticas malas no pretendían significar, matemáticas incorrectas, era
significaba matemáticas molestas. Hay muchas cosas que son técnicamente correctas, pero no muy agradables ...

Si seguimos esta lógica, aquí hay dos opciones que tenemos

x_x sigue siendo un error ... quizás con una sugerencia "quizás quieras usar un punto"
o x_x es el producto escalar (no me encanta esa opción)

Si x y x' son lo mismo, entonces si quieres que (x')*y signifique dot(x,y) eso implica que x*y también es dot(x,y) . No hay forma de salir de eso. Podríamos hacer de x'y y x'*y una operación diferente, pero no estoy seguro de que sea una gran idea. La gente quiere poder poner esto entre paréntesis de la manera obvia y que aún funcione.

Me gustaría señalar que si permitimos que x*x refiera al producto escalar, básicamente no hay vuelta atrás. Eso se incluirá en el código de las personas en todas partes y erradicarlo será una pesadilla. Entonces, isomorfismo natural o no, esto no es pura matemática y tenemos que lidiar con el hecho de que diferentes cosas en una computadora son diferentes.

Aquí hay una discusión práctica sobre cómo distinguir "tuplas ascendentes" y "tuplas descendentes" que me gustan:

http://mitpress.mit.edu/sites/default/files/titles/content/sicm/book-ZH-79.html#% _idx_3310

Evita cuidadosamente palabras como "vector" y "dual", quizás para evitar molestar a la gente. Sin embargo, encuentro convincente la aplicación a derivadas parciales:

http://mitpress.mit.edu/sites/default/files/titles/content/sicm/book-ZH-79.html#% _sec_Temp_453

Otra razón para distinguir M[1,:] y M[:,1] es que actualmente nuestro comportamiento de transmisión permite este comportamiento muy conveniente: M./sum(M,1) es estocástico de columna y M./sum(M,2) es estocástico de fila . Lo mismo podría hacerse para la normalización si "arreglamos" la función norm para permitir la aplicación sobre filas y columnas fácilmente. Por supuesto, aún podríamos tener matrices de retorno sum(M,1) y sum(M,2) lugar de vectores hacia arriba y hacia abajo, pero eso parece un poco fuera de lugar.

Me gusta la idea de los vectores hacia arriba y hacia abajo. El problema es generalizarlo a dimensiones superiores de una manera que no es completamente loca. O simplemente puede hacer de los vectores un caso especial. Pero eso también se siente mal.

Es cierto que arriba / abajo puede ser una teoría separada. El enfoque para generalizarlos parece ser una estructura anidada, que lleva las cosas en una dirección diferente. Es muy probable que haya una razón por la que no los llaman vectores.

Además, x*y = dot(x,y) haría que * no sea asociativo, como en x*(y*z) frente a (x*y)*z . Realmente espero que podamos evitar eso.

Si. Para mí, eso es completamente inaceptable. Quiero decir, técnicamente, el punto flotante * no es asociativo, pero es casi asociativo, mientras que esto sería flagrantemente no asociativo.

Todos estamos de acuerdo en que x*x no debería ser el producto escalar.

La pregunta sigue siendo si podemos pensar en v'w y v'*w como el producto escalar -
Realmente me gusta mucho que funcione de esa manera.

@JeffBezanson y yo estábamos charlando

Una propuesta es la siguiente:

v' es un error para los vectores (esto es lo que hace mathica)
v'w y v'*w es un producto escalar (resultado = escalar)
v*w es una matriz de producto externa (resultado = matriz)

No hay distinción entre filas y vectores de columna. Me gustó esto de todos modos
y estaba feliz de ver el precedente de Mathica
De mathica: http://reference.wolfram.com/mathematica/tutorial/VectorsAndMatrices.html
Debido a la forma en que Mathematica usa listas para representar vectores y matrices, nunca tendrá que distinguir entre vectores de "fila" y "columna"

Los usuarios deben tener en cuenta que no hay vectores de fila ... punto.

Entonces, si M es una matriz

M[1,:]*v es un error ..... (asumiendo que vamos con M[1,:] es un escalar
La advertencia podría sugerir probar dot o '* o M[i:i,:]

M[[1],:]*v o M[1:1,:]*v es un vector de longitud 1 (este es el comportamiento actual de julia de todos modos)

Con respecto al problema estrechamente relacionado en https://groups.google.com/forum/#!topic/julia -users / L3vPeZ7kews

Mathematica comprime secciones de matriz de tipo escalar:

m = Array[a, {2, 2, 2}] 


Out[49]= {{{a[1, 1, 1], a[1, 1, 2]}, {a[1, 2, 1], 
   a[1, 2, 2]}}, {{a[2, 1, 1], a[2, 1, 2]}, {a[2, 2, 1], a[2, 2, 2]}}}

In[123]:= Dimensions[m]
Dimensions[m[[All, 1, All]]]
Dimensions[m[[2, 1, All]]]
Dimensions[m[[2, 1 ;; 1, All]]]

Out[123]= {2, 2, 2}

Out[124]= {2, 2}

Out[125]= {2}

Out[126]= {1, 2}

[Editar: formato de código - @StefanKarpinski]

@alanedelman

asumiendo que vamos con M [1 ,:] es un escalar

¿Quiere decir que M [1 ,:] es solo un vector?

Si, lo siento. Mi mente significaba que M [1 ,:] estaba procesando el escalar 1 :-)

Mathematica usa el punto . lugar del asterisco *
y luego recorre las 9 yardas completas y convierte (vector. vector) en un escalar, exactamente lo que estamos evitando
con el asterisco.

No hay duda de que hay muchos problemas con el período, uno de los cuales es que simplemente no
parece el "punto" en un producto escalar, y otro de los cuales es que choca con
la lectura "pointwise op" de punto,

Unicode proporciona muy bien un carácter llamado "el operador de punto"
(char(8901)) que podemos imaginar ofreciendo

para que (v ⋅ w) convierta en sinónimo de (v'*w)

En resumen, una propuesta actual objeto de debate es

  1. La indexación escalar mata la dimensión por lo tanto
    A[i,:] es un vector como A[:,i,j]
  2. La indexación de vectores es gruesa
    A[ i:i , : ] o A[ [i], : ] devuelve una matriz con una fila
  3. v'w o v'*w es el producto escalar para los vectores (de manera similar v*w' para el producto externo)
  4. v' no está definido para los vectores (señale al usuario permutedims(v,1) ????)
  5. v*A devuelve un vector si A es una matriz
  6. v⋅w también devuelve el producto escalar (pero no llega tan lejos como . de matemática trabajando en matrices
  7. v*w no está definido para los vectores, pero una advertencia podría alertar al usuario con buenas sugerencias que incluyen

Las consecuencias son que

a. si se adhiere a todos los vectores como vectores de columna, todo funciona
segundo. si convierte todo en una matriz, todo funciona, y es fácil convertir todo en una matriz
C. Si su mente no puede distinguir un vector de fila de una matriz de una fila, es probable que se le eduque
cortés y graciosamente
re. Esta notación de puntos es agradable a la vista

La sugerencia 5) me parece muy extraña. Prefiero v'*A para que quede explícito que estás usando el vector dual. Esto es particularmente importante en espacios vectoriales complejos donde el dual no es solo una transformación de "forma".

Quiero hacerme eco de v y normalizar las columnas de la matriz A por esos valores? Actualmente, se puede usar A ./ v' . Esto es muy bueno para la manipulación de datos.

Buena pregunta

Mi esquema no excluye que v'*A tome la conjugada compleja de v y multiplique por A
y todos los otros casos que aún no mencioné explícitamente, pero que fácilmente podría

podríamos eliminar 5
tal vez eso sea deseable
no se ajusta a mi regla de vector de columna

Este enfoque de la radiodifusión es lindo y kludgy
Una solución ahora es A ./ v[:,[1]]

Tiene la ventaja de documentar qué dimensión se está transmitiendo en
y generaliza a matrices de dimensiones superiores

Ah, y la solución v[:,[1]] tiene la virtud de NO tomar el conjugado complejo
que es probablemente lo que el usuario pretende .....

ME GUSTAN ESTOS DOS EJEMPLOS porque el primero es un ejemplo de ALGEBRA LINEAL
donde se desea un conjugado complejo con mucha frecuencia, pero el segundo ejemplo es
un EJEMPLO DE DATOS MULTIDIMENSIONALES donde queremos que las cosas funcionen en todas las dimensiones
no solo para matrices, y es muy probable que no queramos un conjugado complejo

requiere # 552. Esta es la tercera vez que aparece en las últimas dos semanas.

Otra razón para distinguir M [1 ,:] y M [:, 1] es que actualmente nuestro comportamiento de transmisión permite este comportamiento muy conveniente: M./sum(M,1) es estocástico de columna y M./sum(M, 2) es estocástico por filas. Lo mismo podría hacerse para la normalización si "arreglamos" la función de norma para permitir la aplicación sobre filas y columnas fácilmente. Por supuesto, aún podríamos tener matrices de retorno de suma (M, 1) y suma (M, 2) en lugar de vectores hacia arriba y hacia abajo, pero eso parece un poco fuera de lugar.

Me parece que si bien tener el comportamiento de transmisión es bueno algunas veces, terminas teniendo que exprimir esas atenuaciones de unidades adicionales con la misma frecuencia. Por lo tanto, tener que hacer lo contrario algunas veces está bien si el resto del sistema es mejor (y creo que tener las dimensiones escalares reducidas hará que el sistema sea más agradable). Por lo tanto, necesitará una función como

julia> widen(A::AbstractArray,dim::Int) = reshape(A,insert!([size(A)...],dim,1)...)
# methods for generic function widen
widen(A::AbstractArray{T,N},dim::Int64) at none:1

que permitirá código como M ./ widen(sum(M,2),2) o A ./ widen(v,1) (vea el ejemplo de @blakejohnson arriba)

M [:, 0 ,:] yv [:, 0] ?????

Estoy más con @blakejohnson en el tema de la reducción; Personalmente creo que es más claro para squeeze dimensiones que para widen ellos. Sospecho que estaría mirando constantemente los documentos para averiguar si widen inserta la dimensión en o después del índice indicado, y la numeración se vuelve un poco más compleja si desea ampliar varias dimensiones a la vez. (¿Qué hace widen(v, (1, 2)) para un vector v ?) Ninguno de estos son problemas para squeeze .

Independientemente de si ensanchamos o exprimimos por defecto, creo que Julia debería seguir el ejemplo de numpy cuando se trata de ensanchar y permitir algo como v[:, newaxis] . Pero creo que prefiero mantener las dimensiones en lugar de descartarlas, es más difícil detectar un error en el que accidentalmente se ensanchó de la manera incorrecta que cuando se apretó de la manera incorrecta (lo que generalmente dará un error).

En la lista de @alanedelman
siento que

v * A devuelve un vector si A es una matriz

no es bueno.

v_A debería ser un error si A no es 1x1 (desajuste del rango de índice)
v'_A debería ser la forma correcta de hacerlo.

Una forma de manejar este problema es convertir automáticamente el vector v en una matriz nx1 (cuando sea necesario)
y siempre trate v 'como una matriz 1xn (nunca convierta eso en un vector o una matriz nx1)
También permitimos convertir automáticamente la matriz 1x1 en un número de escala (cuando sea necesario).

Siento que esto representa una forma consistente y uniforme de pensar sobre el álgebra lineal. (buenas matemáticas)

Una forma uniforme de manejar todos esos problemas es permitir la conversión automática (¿tipo?) (Cuando sea necesario)
entre matrices de tamaño (n), (n, 1), (n, 1,1), (n, 1,1,1) etc. (pero no entre matrices de tamaño (n, 1) y (1, n) )
(Al igual que convertimos automáticamente un número real en un número complejo cuando es necesario)

En este caso, una matriz de tamaño (1,1) se puede convertir en un número (cuando sea necesario) (Ver # 4797)

Xiao-Gang (físico)

Sin embargo, esto deja a v'_A .... Realmente quiero que v'_A * w funcione

Mi impresión del álgebra lineal en Julia es que está muy organizada como el álgebra matricial, aunque existen escalares y vectores verdaderos (¡lo cual creo que es bueno!)

Consideremos cómo manejar un producto como x*y*z*w , donde cada factor puede ser un escalar, un vector o una matriz, posiblemente con una transposición. El álgebra de matrices define el producto de matrices, donde una matriz tiene un tamaño n x m . Un enfoque sería extender esta definición para que n o m puedan ser reemplazados por absent , que actuaría como un valor de uno en lo que respecta al cálculo del producto. , pero se utiliza para distinguir escalares y vectores de matrices:

  • un escalar sería absent x absent
  • un vector (columna) sería n x absent
  • un vector de fila sería absent x n

Idealmente, nos gustaría arreglar las cosas para que nunca necesitemos representar vectores de fila, pero sería suficiente implementar operaciones como x'*y y x*y' . Tengo la sensación de que este es el tipo de esquema que muchos de nosotros estamos buscando.

Pero estoy empezando a sospechar que prohibir los vectores de fila en este tipo de esquema tendrá un alto costo. Ejemplo: considere cómo necesitaríamos poner entre paréntesis un producto para evitar formar un vector de fila en cualquier paso intermedio: ( a es un escalar, u y v son vectores)

a*u'*v = a*(u'*v) // a*u' is forbidden
v*u'*a = (v*u')*a // u'*a is forbidden

Para evaluar un producto x*y'*z evitando producir vectores de fila, necesitaríamos conocer los tipos de factores antes de elegir el orden de multiplicación. Si el usuario debe hacerlo él mismo, parece un obstáculo para la programación genérica. Y no estoy seguro de cómo Julia pudo hacerlo automáticamente de una manera cuerda.

Otra razón para no arreglar el orden de multiplicación de antemano: creo recordar que hubo una idea de usar programación dinámica para elegir el orden de evaluación óptimo de *(x,y,z,w) para minimizar el número de operaciones necesarias. Cualquier cosa que hagamos para evitar formar vectores de fila probablemente interferirá con esto.

Así que ahora mismo, la introducción de un tipo de vector transpuesto me parece la alternativa más sensata. Eso, o hacer todo como ahora, pero eliminar las dimensiones de singleton finales al mantenerlas, resultaría en un error.

La transposición es solo una forma particular de permutar modos. Si permite v.' donde v es un vector, entonces permutedims(v,[2 1]) debería devolver exactamente lo mismo. Ambos devuelven un tipo de vector de fila especial o introducen una nueva dimensión.

Tener un tipo especial para vectores de fila no me parece una buena solución, porque ¿qué harás con otros tipos de vectores mode-n, por ejemplo, permutedims([1:4],[3 2 1]) ? Les insto a que también tengan en cuenta el álgebra multilineal antes de tomar una decisión.

@toivoh mencionó que

"Un enfoque sería extender esta definición para que nom pueda ser reemplazado por ausente, lo que actuaría como un valor de uno en lo que respecta al cálculo del producto, pero se usa para distinguir escalares y vectores de matrices:

  1. un escalar estaría ausente x ausente
  2. un vector (columna) estaría nx ausente
  3. un vector de fila estaría ausente xn "

En álgebra multilineal (o para tensores de rand alto) la propuesta anterior corresponde al uso ausente para representar
muchos índices de rango 1, es decir, el tamaño (m, n, ausente) puede corresponder a (m, n), (m, n, 1), (m, n, 1,1), etc.

Si usamos esta interpretación de ausente, entonces 1. y 2. está bien y es bueno tenerlo, pero 3. puede no estar bien.
No queremos mezclar matrices de tamaño (1, n) y (1,1, n).

No soy un especialista en teoría tensorial, pero he usado todos los sistemas mencionados anteriormente (_sin_ ningún paquete adicional) para proyectos sustanciales que involucran álgebra lineal.

[TL; DR: pasar al RESUMEN]

Estos son los escenarios más comunes en los que he encontrado la necesidad de una mayor generalidad en el manejo de matrices que las operaciones comunes de matriz-vector:

(1) Análisis funcional: por ejemplo, tan pronto como esté utilizando el hessiano de una función con valores vectoriales, necesita tensores de orden superior para funcionar. Si está escribiendo muchas matemáticas, sería un gran dolor tener que usar una sintaxis especial para estos casos.

(2) Control de evaluación: Por ejemplo, dado cualquier producto que se pueda calcular, uno debería poder calcular cualquier subentidad de ese producto por separado, porque uno podría desear combinarlo con múltiples subentidades diferentes para formar diferentes productos. Por lo tanto, la preocupación de Toivo acerca de, por ejemplo, la prohibición de a*u' no es sólo una cuestión de compilación, sino una preocupación de programación; una variante aún más común es precalcular x'Q para calcular formas cuadráticas x'Q*y1 , x'Q*y2 , ... (donde deben hacerse de forma secuencial).

(3) Simplificación de código: varias veces al tratar con operaciones aritméticas mapeadas sobre conjuntos de datos multidimensionales, he descubierto que 6-7 líneas de código inescrutable de bucle o mapeo de funciones se pueden reemplazar con una o dos operaciones breves de matriz, en sistemas que proporcionan la generalidad adecuada. Mucho más legible y mucho más rápido.

Aquí están mis experiencias generales con los sistemas anteriores:

MATLAB: El lenguaje central está limitado más allá de las operaciones comunes de matriz-vector, por lo que generalmente terminan escribiendo bucles con indexación.

NumPy: Capacidad más general que MATLAB, pero desordenada y complicada. Para casi todas las instancias de problemas no triviales, tuve que consultar la documentación, e incluso entonces a veces descubrí que tenía que implementar yo mismo alguna operación de matriz que, intuitivamente, sentía que debería haberse definido. Parece que hay tantas ideas separadas en el sistema que cualquier usuario y desarrollador tendrá problemas para adivinar automáticamente cómo pensará el otro sobre algo. Por lo general, es posible encontrar una forma breve y eficaz de hacerlo, pero esa forma no siempre es obvia para el escritor o el lector. En particular, creo que la necesidad de dimensiones de ampliación y singleton solo refleja una falta de generalidad en la implementación para aplicar operadores (aunque tal vez algunos lo encuentren más intuitivo).

Mathematica: Limpio y muy general --- en particular, todos los operadores relevantes están diseñados teniendo en cuenta el comportamiento tensorial de orden superior. Además de Dot, vea, por ejemplo, los documentos sobre Transposición, Aplanar / Partición e Interior / Exterior. Al combinar solo estas operaciones, ya puede cubrir la mayoría de los casos de uso de malabarismo de matrices, y en la versión 9 incluso se agregan operaciones de álgebra tensorial adicionales al lenguaje central. La desventaja es que, aunque la forma de Mathematica de hacer algo es limpia y tiene sentido (si conoce el idioma), obviamente puede no corresponder a la notación matemática habitual para hacerlo. Y, por supuesto, la generalidad dificulta saber cómo funcionará el código.

scmutils: para el análisis funcional, es limpio, general y proporciona las operaciones más intuitivas matemáticamente (tanto de escritura como de lectura) de cualquiera de las anteriores. La idea de tupla arriba / abajo es en realidad una extensión más consistente y más general de lo que la gente hace a menudo en matemáticas escritas usando signos de transposición, convenciones de diferenciación y otras nociones semi-estandarizadas; pero todo simplemente funciona. (Para escribir mi tesis doctoral, terminé desarrollando una notación coherente e inequívoca que se asemeja a la notación matemática tradicional, pero isomórfica a la sintaxis SICM de Sussman & Wisdom). También la han utilizado para una implementación de geometría diferencial [1], que ha inspiró un puerto a SymPy [2]. No lo he usado para el análisis de datos, pero esperaría que en un contexto de matriz genérico en el que solo quisiera un tipo de tupla (como la Lista de Mathematica), podría elegir una ("arriba") por convención. Una vez más, la generalidad oscurece las consideraciones de rendimiento para el programador, pero espero que esta sea un área en la que Julia pueda sobresalir.

RESUMEN

Creo que el tipo de vector transpuesto propuesto debería caracterizarse como la tupla "abajo" más general en scmutils, mientras que los vectores regulares serían las tuplas "arriba". Llamarlos como "vector" y "vector transpuesto" probablemente tendría más sentido para la gente que llamarlos "arriba" y "abajo" (a costa de la brevedad). Esto apoyaría tres categorías de uso:

(1) para el análisis de datos, si las personas solo quieren matrices anidadas, solo necesitan "vector";
(2) para álgebra lineal básica matriz-vector, la gente puede usar "vector" y "vector transpuesto" en correspondencia directa con la convención matemática ("matriz" sería equivalente a un "vector transpuesto" de "vector" s);
(3) para las operaciones de tensor de orden superior (donde hay menos estandarización y la gente generalmente tiene que pensar de todos modos), la implementación debe admitir la generalidad completa del sistema aritmético de tuplas de dos tipos.

Creo que este enfoque refleja el consenso emergente anterior para los resultados de varias operaciones, con la excepción de que aquellos casos en los que las publicaciones anteriores consideraron errores ( v' y v*A ) en realidad darían resultados significativos (y a menudo útiles ) resultados.

[1] http://dspace.mit.edu/handle/1721.1/30520
[2] http://krastanov.wordpress.com/diff-geometry-in-python/

@thomasmcoffee suena como si estuvieras abogando por una distinción explícita entre vectores covariantes y contravariantes.

Pensaría en eso como una aplicación común, pero demasiado específica para lo que estoy defendiendo: para mí, eso tiene un significado geométrico que implica una restricción a los tensores rectangulares de números (para representaciones de coordenadas). Dado que imagino (sin experiencia en esta área) que una biblioteca adecuada de funciones de álgebra tensorial con matrices estándar generalmente sería suficiente para este propósito, simpatizo con el punto de Alan de que esto por sí solo no es lo suficientemente convincente para introducir un sistema de dos tipos en el idioma central.

Estoy pensando principalmente en otras aplicaciones que dependen de una estructura anidada más general, por ejemplo, el cálculo de funciones de múltiples argumentos de dimensionalidad mixta, que sería más difícil de desarrollar como un "complemento" más adelante si el lenguaje central no fuera compatible esta distinción. Quizás queremos decir lo mismo.

El problema con los vectores hacia arriba y hacia abajo es que necesita extender la idea a matrices generales. De lo contrario, los vectores se convierten en algo especial y se separan de las matrices, en lugar de simplemente el caso unidimensional de una matriz, lo que conduciría a todo un lío de problemas horribles. He pensado mucho en cómo hacer eso, pero no he encontrado nada aceptable. Si tiene buenas ideas sobre cómo generalizar de forma sensata los vectores hacia arriba y hacia abajo en matrices, me encantaría escucharlas.

Solo intento extrapolar esta idea. Según tengo entendido, para usar una matriz para calcular con vectores ascendentes y descendentes, debe indicar para cada dimensión si está hacia arriba o hacia abajo. En general, esto podría lograrse envolviendo una matriz en algo como

immutable UpDownTensor{T, N, UPMASK} <: AbstractArray{T, N}
    A::AbstractArray{T, N}
end

donde UPMASK sería una máscara de bits para indicar qué dimensiones están arriba. Luego, las operaciones en matrices no encapsuladas podrían implementarse proporcionando un UPMASK predeterminado como una función de N : los vectores por defecto serían un solo arriba, matrices el primero arriba y el segundo abajo; entonces no estoy seguro de cómo se continuaría razonablemente.

Algunos pensamientos aleatorios:

  • ¿Las formas cuadráticas / bilineales estarían mejor representadas por dos dimensiones hacia abajo?
  • Si la transposición se correspondería con cambiar la amplitud hacia arriba o hacia abajo de cada dimensión, supongo que también obtendríamos un tipo de matriz transpuesta con la primera dimensión hacia abajo y la segunda hacia arriba.
  • Los patrones ascendentes que corresponden al valor predeterminado se pueden representar directamente mediante la matriz subyacente en lugar de envolverla.

Bueno, esta es ciertamente una generalización del tipo Transposed , y ciertamente tiene algún mérito. No estoy seguro de si es factible.

Creo que la sugerencia de Toivo es una realización razonable de lo que estaba defendiendo anteriormente. Para mí, el valor predeterminado más sensato es continuar alternando direcciones en órdenes superiores: por ejemplo, si alguien proporciona los componentes de una serie de potencia como matrices no envueltas, esto haría lo correcto.

Pero en una reflexión más profunda, creo que podría ser muy poderoso combinar ambas ideas: (1) distinciones entre vectores ascendentes y descendentes y (2) distinciones entre matrices y vectores. Entonces, podría tener objetos en los que algunas dimensiones aumentan, otras disminuyen y otras son "neutrales". La razón para distinguir matrices y vectores es que, semánticamente, las matrices son para la organización (recolectando múltiples cosas del mismo tipo), mientras que los vectores son para la coordinación (que representan espacios multidimensionales). El poder de combinar ambas distinciones en un objeto es que puede servir para ambos propósitos simultáneamente. Las dimensiones neutrales se tratarían de acuerdo con las reglas de transmisión, mientras que las dimensiones arriba / abajo se tratarían de acuerdo con las reglas aritméticas de tensores.

Volviendo a un ejemplo anterior mío, suponga que está calculando varias formas cuadráticas x'Q*y1, x'Q*y2, ... para diferentes vectores y1, y2, ... . Después de SICM, denote tuplas hacia arriba (vectores de columna) por (...) y tuplas hacia abajo (vectores de fila) por [...] . Si quisiera hacer todo esto a la vez, y está atascado solo con arriba / abajo, la forma convencional sería combinar yi en una matriz Y = [y1, y2, ...] usando una tupla abajo tuplas hacia arriba) y calcula r = x'Q*Y , que le da los resultados en una tupla hacia abajo r . Pero, ¿qué sucede si desea multiplicar cada uno de estos resultados por un vector (columna) v ? No puede simplemente hacer r*v , porque obtendrá una contracción (producto escalar). Puede convertir r en una tupla ascendente y luego multiplicar, lo que le da los resultados en una tupla ascendente (de tuplas ascendentes). Pero suponga que para el siguiente paso necesita una tupla descendente. Semánticamente, tiene una dimensión que pasa por su cálculo que solo representa una colección de cosas, que siempre quiere que se difunda; pero para lograr esto en el mundo estrictamente arriba / abajo, debe seguir haciendo conversiones arbitrarias dependientes del contexto para obtener el comportamiento correcto.

Por el contrario, suponga que también tiene tuplas neutrales (matrices), indicadas {...} . Entonces, naturalmente, escribe ys = {y1, y2, ...} como una matriz (de tuplas ascendentes), de modo que r = x'Q*ys es una matriz y r*v también es una matriz (de tuplas ascendentes). Todo tiene sentido y no se requieren conversiones arbitrarias.

Stefan sugiere que distinguir matrices 1-D de vectores ascendentes / descendentes es desastroso, pero creo que este problema se resuelve por el hecho de que la mayoría de las funciones tienen sentido al operar en vectores _o_ en matrices, pero NO en cualquiera de las dos. (O, en matrices _o_ en matrices de vectores _o_ en vectores de matrices _o_ en matrices de matrices, pero NO _ cualquiera de las dos_. Y así sucesivamente. Entonces, con las reglas de conversión apropiadas, no he pensado en un caso común que no funcionaría lo correcto automáticamente. ¿Quizás alguien pueda?

Al mirar más profundamente [1], descubrí que scmutils en realidad distingue lo que ellos llaman "vectores" de las tuplas arriba y abajo debajo del capó; pero actualmente las reglas de conversión están configuradas para que estos "vectores" se asignen a tuplas ascendentes (como propuse anteriormente) cada vez que ingresen al mundo ascendente / descendente, con la advertencia de que "Nos reservamos el derecho de cambiar esta implementación para distinguir Esquema de vectores de tuplas superiores ". (Quizás alguien en el campus pueda preguntarle a GJS si tiene alguna idea específica en mente). El sistema Sage [2] separa en gran medida el manejo de matrices de vectores y matrices (actualmente no hay soporte central para tensores), y los únicos problemas que he experimentado con esto tiene que ver con su falta de conversión incorporada entre ellos en casos que obviamente tendrían sentido.

[1] http://groups.csail.mit.edu/mac/users/gjs/6946/refman.txt --- comenzando en "Objetos estructurados"
[2] http://www.sagemath.org/

Estaba hablando con @jiahao en la mesa del almuerzo y él mencionó que el equipo de Julia estaba tratando de averiguar cómo generalizar las operaciones de álgebra lineal a matrices de dimensiones superiores. Hace dos años pasé varios meses pensando en esto porque lo necesitaba para KroneckerBio. Quería compartir mi enfoque.

Consideremos solo el producto entre dos matrices por el momento. Otras operaciones tienen una generalización similar. Los tres tipos de productos más comunes cuando se trata de matrices son el producto exterior, el producto interior y el producto por elementos. Por lo general, pensamos en realizar operaciones como esta entre dos objetos, como inner(A,B) o A*B . Sin embargo, al realizar estas operaciones en matrices de dimensiones superiores, no se realizan entre las matrices como un todo, sino entre dimensiones particulares de las matrices. Varias suboperaciones externas / internas / por elementos ocurren en una sola operación entre dos matrices y cada dimensión de cada matriz debe asignarse exactamente a una suboperación (ya sea explícitamente o por defecto). Para los productos de interior y de elementos, una dimensión de la izquierda se debe emparejar con una dimensión de igual tamaño a la derecha. No es necesario emparejar las dimensiones externas del producto. La mayoría de las veces, el usuario está haciendo un producto interno o un producto de elementos entre un par de dimensiones y un producto externo para todos los demás. El producto externo tiene un buen valor predeterminado porque es el más común y no es necesario emparejarlo.

Por lo general, pienso en las dimensiones como nombradas en lugar de ordenadas, al igual que los ejes x, y y z de una gráfica. Pero si desea que los usuarios realmente puedan acceder a las matrices mediante la indexación ordenada (como A[1,2,5] lugar de A[a1=1, a3=5, a2=2] ), entonces debe tener un procedimiento consistente para ordenar los resultados de una operación. Propongo ordenar el resultado enumerando todas las dimensiones de la primera matriz y luego enumerando todas las dimensiones de la segunda matriz. Todas las dimensiones que participaron en un producto interno se exprimen, y para las dimensiones que participaron en un producto de elementos, solo se exprime la dimensión de la segunda matriz.

Voy a inventar una notación para esto. Siéntete libre de Juliafylo. Sea A una matriz que sea a1 por a2 por a3 y sea B una matriz que sea b1 por b2 . Digamos que array_product(A, B, inner=[2, 1], elementwise=[3, 2]) tomaría el producto interno entre las dimensiones a2 y b1 , el producto por elementos entre a3 y b2 , y el producto externo de a1 . El resultado sería una matriz que es a1 por a3 .

Debe quedar claro que ningún operador binario o unario va a tener mucho significado en el contexto de matrices de mayor dimensión. Necesita más de dos argumentos para especificar qué hacer con cada dimensión. Sin embargo, puede recuperar la facilidad del álgebra lineal haciendo que los operadores de Matlab sean abreviaturas para las operaciones de matriz solo en las dos primeras dimensiones:

A*B Matlab es array_product(A, B, inner=[2,1]) .

A.' Matlab es permute(A, B, [2,1]) donde permute mantiene sin cambios todas las dimensiones por encima del recuento del tercer argumento.

Puede elegir si arrojar errores o no cuando la dimensionalidad de las matrices es mayor que 2 o incluso no es igual a 2, como lo hace Mathematica con transposiciones vectoriales. Si está utilizando solo los cálculos de matrices generales, no tiene que decidir si acepta o no la sugerencia de @wenxgwen de interpretar todas las matrices (n, m) como (n, m, 1) y (n, m, 1 , 1). Solo cuando use los operadores de álgebra lineal u otros operadores que esperan una matriz o una dimensionalidad particular, debe tomar esta decisión. Me gusta la sugerencia de @wenxgwen , porque hay pocas desventajas en un lenguaje escrito dinámicamente.

Escribí una descripción más

¡Gracias por la perspectiva! Encontré esto bastante esclarecedor para entender qué tipo de bestia es realmente un producto de matriz general * matriz.

Puede ser interesante cruzar las propuestas de multiplicación de matrices multidimensionales con la semántica propuesta para un operador de multiplicación de matrices en PEP 0465 . En particular:

Las entradas vectoriales 1d se promueven a 2d anteponiendo o añadiendo un '1' a la forma, se realiza la operación y luego se elimina la dimensión añadida de la salida. El 1 siempre se agrega en el "exterior" de la forma: antepuesto para los argumentos izquierdos y adjunto para los argumentos derechos. El resultado es que matrix @ vector y vector @ matrix son legales (asumiendo formas compatibles), y ambos devuelven vectores 1d; vector @ vector devuelve un escalar ... Un inconveniente de esta definición para vectores 1d es que hace que @ no sea asociativo en algunos casos ((Mat1 @ vec) @ Mat2! = Mat1 @ (vec @ Mat2)). Pero este parece ser un caso en el que la practicidad vence a la pureza

Eludir la escritura en Python causa un problema especial. Naturalmente, las matrices y las matrices deben ser intercambiables (los mismos datos subyacentes). Pero debido a que Python desaconseja la verificación de un tipo, las matrices no se envían a la interfaz correcta al comienzo de una función que espera una matriz y viceversa. Es por eso que deben tener diferentes caracteres de operador. Julia con verificación de tipo en tiempo de ejecución y métodos convert no sufre esta ambigüedad.

Desde PEP 0465:

Un inconveniente de esta definición para vectores 1d es que hace que @ no sea asociativo en algunos casos ((Mat1 @ vec) @ Mat2! = Mat1 @ (vec @ Mat2))

En particular, este tipo de definición puede producir resultados incorrectos en Mathematica, porque se supone que Dot ( . ) es asociativo ( Flat ) cuando se evalúa simbólicamente (como con f continuación, pero no con g ):

In[1]:= f=X.(y.Z);
g:=X.(y.Z)

In[3]:= Block[{
X=Array[a,{2,2}],
y=Array[b,2],
Z=Array[c,{2,2}]
},{f,g}]

Out[3]= {{(a[1,1] b[1]+a[1,2] b[2]) c[1,1]+(a[2,1] b[1]+a[2,2] b[2]) c[2,1],(a[1,1] b[1]+a[1,2] b[2]) c[1,2]+(a[2,1] b[1]+a[2,2] b[2]) c[2,2]},{a[1,1] (b[1] c[1,1]+b[2] c[2,1])+a[1,2] (b[1] c[1,2]+b[2] c[2,2]),a[2,1] (b[1] c[1,1]+b[2] c[2,1])+a[2,2] (b[1] c[1,2]+b[2] c[2,2])}}

In[4]:= SameQ@@Expand[%]
Out[4]= False

De @drhagen :

Julia con verificación de tipo en tiempo de ejecución y métodos convert no sufre esta ambigüedad.

Esta es la razón por la que creo que la solución correcta para Julia _debería_ dejar que los datos mismos distingan entre la semántica en forma de matriz (para transmisión universal) y la semántica en forma de tensor (para una posible contracción).

No soy de ninguna manera una autoridad aquí, pero no creo que el tipo de colección de dimensiones arbitrarias generales ( Array ) deba admitir un operador que haga un producto punto. Este operador simplemente no se puede definir de manera sensata para este tipo porque el producto escalar puede estar entre dos dimensiones cualesquiera, lo que requiere argumentos adicionales que no se pueden suministrar a un operador binario. Lo mismo ocurre con todas las operaciones de álgebra lineal, inv , transpose , etc.

Para operar en el campo matemático del álgebra lineal, debería haber tres tipos más, Matrix , ColumnVector y RowVector , en los que todos los operadores y funciones normales del álgebra lineal trabajar como de costumbre.

Ahora que la estructura de tipos está bien definida, puede hacerlo más fácil para el usuario agregando conversión implícita de Matrix a Array{2} , ColumnVector a Array{1} , y RowVector a Array{2} (no estoy seguro de este), Array{2} a Matrix y Array{1} a ColumnVector .

Mi propuesta anterior (https://github.com/JuliaLang/julia/issues/4774#issuecomment-32705055) permite que cada dimensión de una estructura multidimensional distinga si tiene neutral ("colección" / "matriz"), arriba (" columna "), o semántica hacia abajo (" fila "). Creo que lo que estás describiendo es un caso especial.

La ventaja de este enfoque general es que incluso en cálculos con muchas dimensiones de datos o espacio, puede hacer que los operadores hagan lo correcto sin especificar explícitamente en qué dimensiones deben operar. Creo que estamos de acuerdo en que, al menos en Julia, es mucho más intuitivo y legible para un usuario especificar una vez el significado de los datos de entrada eligiendo parámetros de tipo, que tener que especificar el significado de cada operación llamando a cada instancia con argumentos adicionales que dan índices dimensionales. Las conversiones implícitas o explícitas aún pueden usarse, con generalidad dimensional completa, en los casos en que el significado deba cambiarse a mitad de camino de maneras inusuales.

@thomasmcoffee me gusta mucho tu propuesta. Implementé algo vagamente similar en un DSL (hace mucho tiempo, muy lejos) con algunos principios rectores (también conocidos como opiniones personales):

  1. La noción de duales como distintos es vital para cualquier semántica sensiblemente autoconsistente.
  2. La aplicación ad hoc del álgebra tensorial con operadores parametrizados (o cualquier cosa externa a los datos para el caso) es estéticamente muy desagradable.

Las mayores quejas que recibí en ese entonces (y eran ruidosas) eran exactamente el tipo de inconveniente que resuelve su semántica trivalente (agregando una noción de colección neutral). ¡Agradable! Esa idea nunca se me ocurrió, pero tiene mucho sentido ahora que la publicaste. Realmente me gustaría usar un sistema así, y me refiero al trabajo real. ¡Sería bueno si Julia pudiera adaptarse a esto!

Lo que parecen estar describiendo son tensores regulares. Dudo que sea un caso de uso bastante común por sí solo para justificar estar en la biblioteca estándar, ya que los otros dos casos de uso (colecciones y álgebra lineal) son mucho más comunes. Sin embargo, si pudiera integrarse sin problemas, lo apoyaría. ¿Podría dar algunos ejemplos de cómo se verían algunas operaciones comunes en este sistema, como la multiplicación de matrices de vectores, la multiplicación de matrices escalares, la distribución de la suma de una matriz sobre una matriz de matrices, etc.?

Creo que tienes razón, David. Realmente estamos hablando de dos casos de uso.

El subconjunto de álgebra lineal es el más comúnmente necesario para la mayoría de las personas
decir. Incluso allí defiendo mantener una distinción entre v y v '.

Lo que realmente me gustaría (insertar información de codicia aquí) son tensores con
estado de primera clase (o cercano) ... cerca de la velocidad nativa (en el caso límite,
en comparación con el rendimiento de Álgebra lineal) con una sintaxis fácil, sin
problemas de mecanografía, con co / contravarianza codificada en los datos, no impuesta a
los operadores. Una vez que defina la semántica de los datos, las operaciones deberían
solo trabajo. Mecanografía de pato tensoral.

Quizás los tensores y TDT pertenecen a un paquete y no al núcleo, solo en
motivos de relativa popularidad. Pero como la declaración de Julia de
independencia dice, Julia nace de la codicia. Y como dice Gordon Gecko,
La codicia es buena. :)
El 21 de marzo de 2014 a las 3:14 a. M., "David Hagen" [email protected] escribió:

Lo que parecen estar describiendo son tensores regulares. Dudo que sea un
caso de uso lo suficientemente común solo para justificar estar en la biblioteca estándar, como
los otros dos casos de uso (colecciones y álgebra lineal) son mucho más
común. Sin embargo, si pudiera integrarse sin problemas, lo apoyaría.
¿Podría dar algunos ejemplos de cómo se verían algunas operaciones comunes?
bajo este sistema, como la multiplicación de matrices vectoriales, matriz escalar
multiplicación, distribuyendo la suma de una matriz sobre una matriz de
matrices, etc.?

Responda a este correo electrónico directamente o véalo en Gi
.

Creo que definitivamente se puede lograr una integración perfecta con una familia tipográfica lo suficientemente rica. Extendiendo https://github.com/JuliaLang/julia/issues/4774#issuecomment -32693110 de Toivo arriba, teóricamente podría comenzar así:

immutable AbstractTensorArray{T, N, UPMASK, DOWNMASK} <: AbstractArray{T, N}
    A::AbstractArray{T, N}
end
# where !any(UPMASK & DOWNMASK)

typealias AbstractColumnVector{T} AbstractTensorArray{T, 1, [true], [false]}
typealias AbstractRowVector{T} AbstractTensorArray{T, 1, [false], [true]}
typealias AbstractMatrix{T} AbstractTensorArray{T, 2, [false, true], [true, false]}

(actualmente AbstractMatrix{T} simplemente alias AbstractArray{T, 2} ; potencialmente, se podría usar otro nombre aquí)

A partir de aquí, las siguientes implementaciones parecen lógicas:

  1. El método transpose generalizado, después de reorganizar las dimensiones y los índices UPMASK y DOWNMASK correspondientes, intercambia UPMASK y DOWNMASK. Las dimensiones neutrales no se verían afectadas.
  2. Cualquier subtipo AbstractArray{T, N} se convierte normalmente de forma predeterminada en los subtipos alternativos correspondientes de AbstractTensorArray{T, N, [..., false, true, false, true], [..., true, false, true, false]} en las operaciones de tensor. Esto conserva la semántica existente de la sintaxis de matriz especial de Julia para vectores y matrices.
  3. Un método constructor (digamos, array ) por AbstractTensorArray se usa para producir dimensiones neutrales, y puede combinar otros AbstractTensorArray s (o tipos convertibles a los mismos) para crear un AbstractTensorArray combinado

Considerando los ejemplos de @drhagen :

multiplicación de matrices vectoriales, multiplicación de matrices escalares

c = 1               # Int
v = [1, 2]          # Array{Int, 1}
M = [[1, 2] [3, 4]] # Array{Int, 2}

# scalar-array
c * M               # UNCHANGED: *(Int, Array{Int, 2}) => Array{Int, 2}

# matrix-vector
M * v               # *(Array{Int, 2}, Array{Int, 1}) => *(Matrix{Int}, ColumnVector{Int}) => ColumnVector{Int}

# vector-matrix
v' * M              # transpose(Array{Int, 1}) => transpose(ColumnVector{Int}) => RowVector{Int}
                    # *(RowVector{Int}, Array{Int, 2}) => *(RowVector{Int}, Matrix{Int}) => RowVector{Int}

# (1-array)-(2-array)
v .* M              # UNCHANGED: .*(Array{Int, 1}, Array{Int, 2}) => Array{Int, 2}

(usando Matrix con una definición correspondiente a la definición de AbstractMatrix anterior)

distribuir la adición de una matriz sobre una matriz de matrices

Entiendo que esto significa, semánticamente, la adición de un vector sobre una matriz de vectores, la adición de una matriz sobre una matriz de matrices, y así sucesivamente:

# vector-(vector-array)
ws = array([1, 2], [3, 4])
                    # TensorArray{Int, 2, [false, true], [false, false]}
v + ws              # +(Array{Int, 1}, TensorArray{Int, 2, [false, true], [false, false]}) => +(ColumnVector{Int}, TensorArray{Int, 2, [false, true], [false, false]}) => TensorArray{Int, 2, [false, true], [false, false]}
# => array([2, 4], [4, 6])

# array-(vector-array)
u = array(1, 2)     # TensorArray{Int, 1, [false], [false]}
u + ws              # +(TensorArray{Int, 1, [false], [false]}, TensorArray{Int, 2, [false, true], [false, false]}) => TensorArray{Int, 2, [false, true], [false, false]}
# => array([2, 3], [5, 6])
# alternatively:
v .+ ws             # .+(Array{Int, 1}, TensorArray{Int, 2, [false, true], [false, false]}) => TensorArray{Int, 2, [false, true], [false, false]}
# => array([2, 3], [5, 6])
# same effect, but meaning less clear:
v .+ M              # UNCHANGED: .+(Array{Int, 1}, Array{Int, 2}) => Array{Int, 2}
# => [[2, 4] [4, 6]]

# matrix-(matrix-array)
Ns = array([[1, 2] [3, 4]], [[5, 6] [7, 8]])
                    # TensorArray{Int, 2, [false, false, true], [false, true, false]}
M + Ns              # +(Array{Int, 2}, TensorArray{Int, 2, [false, false, true], [false, true, false]}) => +(Matrix{Int}, TensorArray{Int, 2, [false, false, true], [false, true, false]}) => TensorArray{Int, 2, [false, false, true], [false, true, false]}
# => array([[2, 4] [6, 8]], [[6, 8] [10, 12]])

Considerando mi ejemplo anterior de escalar un vector v por varias formas cuadráticas diferentes x'M*w1, x'M*w2, ... , para un resultado final x'M*w1*v, x'M*w2*v, ... :

x = v
x' * M * ws * v     # *(RowVector{Int}, Array{Int, 2}) => *(RowVector{Int}, Matrix{Int}) => RowVector{Int}
                    # *(RowVector{Int}, TensorArray{Int, 2, [false, true], [false, false]}) => TensorArray{Int, 1, [false], [false]}
                    # *(TensorArray{Int, 1, [false], [false]}, Array{Int, 1}) => *(TensorArray{Int, 1, [false], [false]}, ColumnVector{Int}) => TensorArray{Int, 1, [false, true], [false, false]}
# => array([27, 54], [59, 118])

En esta implementación teórica, he asumido que AbstractArray se deja solo y, por lo tanto, AbstractTensorArray forma su propio "espacio" en la jerarquía de tipos. Las cosas podrían simplificarse si toda la familia AbstractArray simplemente fuera reemplazada por AbstractTensorArray , pero esa es otra discusión.

En el contexto de un paquete para algo en física cuántica, he estado jugando con la definición de mi propio tipo de tensor (en realidad, más de uno). Inicialmente, también tenía alguna noción de índices que vienen en dos sabores (arriba y abajo, entrante y saliente, covariante y contravariante, como quiera llamarlo), con esta información almacenada en un campo en el tipo o incluso en parámetro de tipo. Después de un tiempo, decidí que esto era una gran molestia. Es mucho más fácil asociar los índices del tensor a un espacio vectorial (lo que ya estaba haciendo de todos modos) y permitir que este espacio vectorial tenga un dual que es diferente. En la práctica, por espacio vectorial me refiero simplemente a un tipo de Julia simple que envuelve la dimensión del espacio y si es dual o no. Si un índice tensorial está asociado a un espacio vectorial normal, es un índice hacia arriba, si está asociado a un índice dual, es un índice hacia abajo. ¿Quiere trabajar con tensores / matrices para los que no hay distinción? Simplemente defina un tipo de espacio vectorial diferente que no distinga entre el espacio vectorial normal y su dual.

En esta propuesta, solo se pueden contraer índices tensoriales que están asociados a espacios vectoriales que son duales entre sí. ctranspose (= conjugación hermitiana) mapea el espacio vectorial de cada índice en su dual (junto con la permutación de los índices en el caso de una matriz, y una definición preferida para tensores de orden superior), etc.

Por supuesto, la transposición normal y la conjugación compleja no están realmente bien definidas en esta configuración (es decir, estos no son conceptos independientes de la base)

Minimalistamente, se parece a esto:

immutable Space
    dim::Int
    dual::Bool
end
Space(dim::Int)=Space(dim,false) # assume normal vector space by default
dual(s::Space)=Space(s.dim,!s.dual)

matrix=Tensor((Space(3),dual(Space(5))))
# size is no longer sufficient to characterise the tensor and needs to be replaced by space
space(matrix) # returns (Space(3),dual(Space(5))) 
space(matrix') # returns (Space(5),dual(Space(3)))

Por supuesto, podrías inventar alguna sintaxis para no tener que escribir en Space constantemente. Puede crear un tipo de espacio diferente para el cual dual (s) == s para tener tensores que no distingan entre índices ascendentes y descendentes, y así sucesivamente. Pero, por supuesto, no hay forma de que esto pueda integrarse en el tipo de matriz estándar de Julia sin romper todo ...

Siempre me he preguntado por qué no existe una relación más estrecha entre cómo se usan los tensores en ingeniería / física y cómo se manejan en programas de software matemático. Encontré una interesante conversación de intercambio de pilas sobre el tema ... http://math.stackexchange.com/questions/412423/differences-between-a-matrix-and-a-tensor. Aquí, también, fue una buena publicación de referencia.
http://www.mat.univie.ac.at/~neum/physfaq/topics/tensors

Uso mucho Matlab para mi informática científica diaria, pero soy un novato en Julia. Aquí, me doy cuenta de que hay muchas discusiones sobre la multiplicación y transposición de matrices de alta dimensión u otras operaciones de matriz similares. Sugiero echar un vistazo a http://www.mathworks.com/matlabcentral/fileexchange/8773-multiple-matrix-multiplications--with-array-expansion-enabled

Básicamente sigue la sintaxis similar a lo que @drhagen ha mencionado en una publicación anterior, como array_product (A, B, inner_A_dim = [1, 2], inner_B_dim = [3, 4]) para un producto entre las matrices A y B en las dimensiones internas dadas.

Este es un paquete de Matlab que puede aplicar operaciones de multiplicación o transposición en algunas dimensiones seleccionadas. Hay un manual en el paquete sobre cómo implementar estas operaciones en Matlab, pero creo que la teoría matemática debería aplicarse también a otros lenguajes. Su idea es implementar operaciones de matriz evitando el uso de bucles For y confiando principalmente en la remodelación de matriz, etc. Entonces, es extremadamente rápido en Matlab. No sé si a Julia le gustan más las operaciones vectorizadas o las operaciones desvectorizadas (parece la última). Creo que la operación vectorizada es una ventaja para las operaciones de matriz de alta dimensión, si el núcleo lo admite. Quizás deberíamos considerar sinceramente este tipo de operaciones de matriz en esta etapa.

Para su referencia: Otra implementación similar en Matlab para la operación INV está aquí: http://www.mathworks.com/matlabcentral/fileexchange/31222-inversion-every-2d-slice-for-arbitrary-multi-dimension-array

También tenga en cuenta que, después del lanzamiento del paquete de soporte de operación de matriz Matlab en 2005, el registro de descarga se mantiene en un nivel alto como hasta hoy. Como en mi experiencia práctica, las operaciones de arreglos se utilizan con mucha frecuencia en física y otras áreas. Yo diría que si Julia tiene funciones similares para operar matrices con tamaños arbitrarios, ¡el juego se volverá muy interesante!

otro voto aquí para la solución propuesta por @alanedelman hacia la cima. aquí hay un ejemplo motivador.

en este momento, un segmento de fila es una matriz 2d, mientras que un segmento de columna es una matriz 1d; que es extrañamente asimétrico y feo:

julia> A = randn(4,4)
4x4 Array{Float64,2}:
  2.12422    0.317163   1.32883    0.967186
 -1.0433     1.44236   -0.822905  -0.130768
 -0.382788  -1.16978   -0.19184   -1.15773
 -1.2865     1.21368   -0.747717  -0.66303

julia> x = A[:,1]
4-element Array{Float64,1}:
  2.12422
 -1.0433
 -0.382788
 -1.2865

julia> y = A[1,:]
1x4 Array{Float64,2}:
 2.12422  0.317163  1.32883  0.967186

en particular, significa que no puedo multiplicar una fila por una columna y extraer un número sin hacer una manipulación terriblemente fea como

julia> dot(y[:],x)
2.4284575954571106
julia> (y*x)[1]
2.42845759545711

No es una propuesta coherente a menos que haga de '* un operador especial, lo cual es bastante dudoso, ya que entonces x'*y y (x')*y no significan lo mismo. Además, haría que la multiplicación no sea asociativa.

Entiendo las dificultades con x_y 'e y'_x. Puede ser mejor tratar
productos internos y externos como operaciones separadas, utilizando, por ejemplo, dot (). (Quizás
también usando cdot?)

Pero, ¿cuáles son los argumentos a favor de tener un corte a lo largo del primer
dimensión devuelve un objeto cuya dimensión es diferente a un segmento a lo largo
la segunda dimensión? Por coherencia, parece que cada vez que corta,
la dimensión del objeto resultante debe reducirse en uno.

El miércoles 16 de julio de 2014 a las 8:17 p.m. Stefan Karpinski [email protected]
escribió:

No es una propuesta coherente a menos que haga '* un operador especial, que
es bastante dudoso, ya que entonces x'_y y (x ') _ y no significan lo mismo.
Además, haría que la multiplicación no sea asociativa.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -49254346.

Madeleine Udell
Candidato a Doctorado en Ingeniería Computacional y Matemática
Universidad Stanford
www.stanford.edu/~udell

@madeleineudell , estoy de acuerdo contigo, pero ese es un tema diferente, consulta el n. ° 5949. Aunque ese tema parece estar cerrado, no recuerdo que hubiera un acuerdo o una conclusión clara.

Una vez que cambiemos a las vistas de matriz, será más fácil explorar esas direcciones. En particular, decir slice(A, i, :) da el comportamiento que desea. (Lo hace ahora mismo, pero a costa de introducir un tipo más lento, el SubArray).

Desde un punto de vista puramente matemático, todos los problemas presentados aquí provienen de una combinación de (y confusión entre) lo que entendemos por matrices y lo que entendemos por vectores / tensores / matrices. Las matrices, conceptualmente, son simplemente listas (o, en el caso de matrices n-dim, listas de listas). Como tal, no existe una especificación natural para operaciones como multiplicación de matrices, transposición, etc. Mientras que funciones como permutedims, operaciones de elementos y operaciones específicas de eje (media, mediana, etc.) tienen sentido y se pueden definir de forma única en un de forma natural, operaciones como los productos punto no pueden ser.

Como se mencionó anteriormente, los vectores y tensores son objetos geométricos, y aunque es posible representarlos usando matrices, estas representaciones no contienen la misma riqueza de estructura que los objetos matemáticos que representan. La transposición de una matriz de 1 dim es un no-op; la transposición de un vector es su dual. La transposición de una matriz de 2 dim se puede definir de forma única y natural como la permutación de sus dimensiones, pero esto no es generalmente cierto para los tensores: mientras que el caso natural es válido para tensores de rango (1,1) (también conocidos como matrices), un El tensor de rango (2,0) se transpone a un tensor de rango (0,2). Nuevamente, al tratar los tensores como matrices, se pierde la información geométrica que hace que los tensores sean tensores.

Esto es importante al definir operaciones como productos punto. Un producto escalar tiene un significado geométrico específico (la proyección de un vector en el espacio dual definido por un segundo vector) y, por lo tanto, una definición coherente de los productos escalares requiere la preservación de la información geométrica contenida en los vectores. El uso de ciertas suposiciones podría hacer posible el uso de matrices y aún cubrir la mayoría de los casos de uso, pero estas suposiciones son confusas (como se ve en las diversas propuestas en este hilo) y en realidad dificultan las cosas para cualquiera que necesite la estructura más rica de tensores. .

Por lo tanto, considere esto como un voto fuerte a favor de la sugerencia de thomasmcoffee de incluir un tipo AbstractTensor más rico. Mi preferencia personal sería que las operaciones como la transposición y los productos punto ni siquiera estuvieran definidas para las matrices, pero como sospecho que la mayoría de la gente no compartiría esa opinión, al menos me gustaría tener la capacidad de crear verdaderos tensores si surgiera la necesidad.

La implicación práctica de esta perspectiva parece ser que las matrices deben identificarse con un subconjunto de tensores, y la transposición de una matriz 1-d debería dar DualVector o tal vez un error. Mi opinión es que esto es análogo a las operaciones con números reales que dan números complejos.

Mi perspectiva sería que la familia AbstractArray general, un contenedor de datos (multidimensional), es lo suficientemente general como para ser una parte indispensable de cualquier lenguaje de programación técnica. Un tensor que sigue estrictas reglas matemáticas, aunque me preocupo mucho por él, es un buen objeto para un paquete dedicado. De hecho, estoy trabajando en algo similar a lo especificado por https://github.com/Jutho/TensorToolbox.jl . Hasta ahora está indocumentado y en gran parte no se ha probado. Lo escribí para las cosas que necesito personalmente en la física cuántica de muchos cuerpos, pero espero que esté construido de una manera que sea lo suficientemente general y extensible para ser útil para la gran comunidad de matemáticos y físicos que se preocupan por trabajar con tensores.

Para dar algunos detalles (copiado del foro JuliaQuantum): Decidí definir una nueva jerarquía de tipos para tensores, que es independiente del tipo AbstractArray de Julia (aunque el Tensor básico es solo un contenedor para Array). Se supone que esta jerarquía de tipos funciona de una manera un poco más formal. Los índices tensoriales están asociados a espacios vectoriales (en adelante, espacios índice), y si el tipo de espacio vectorial al que está asociado el índice tensorial es diferente de su dual, esto corresponde a un tensor que distingue entre índices covariantes y contravariantes.

Entonces, la primera parte del paquete es la parte abstracta para definir espacios vectoriales, donde hago coincidir la jerarquía de tipos de los objetos de Julia con la jerarquía matemática de los espacios vectoriales. Un espacio vectorial general V se presenta en cuatro variedades, correspondientes a la teoría de representación del grupo lineal general en V, es decir, V en sí mismo (representación fundamental), conj (V), dual (V) y dual (conj (V)). Para espacios vectoriales reales, conj (V) = V y solo hay V y dual (V), correspondientes a vectores contravariantes y covariantes. Luego están los espacios de productos internos, y en el nivel superior de la jerarquía están los espacios euclidianos, que son espacios de productos internos con un producto interno euclidiano estándar (es decir, una base ortogonal). En física, también es útil pensar en espacios vectoriales que se descomponen en diferentes sectores, es decir, que se clasifican, por ejemplo, mediante representaciones irreductibles de acciones de simetría.

Los tensores son objetos que viven en (un subespacio de) el producto tensorial de algunos espacios vectoriales elementales. Sin embargo, aparte del tensor estándar, que es un objeto que vive en el espacio del producto tensorial de sus espacios índice, se podrían construir tensores que vivan, por ejemplo, en el sector invariante de un producto tensorial de espacios clasificados por irreps, el subespacio simétrico o antisimétrico de un producto tensorial de espacios idénticos, ... Se podrían tener espacios vectoriales fermiónicos como espacios índice, lo que implica que una permutación de los índices tensoriales inducirá ciertos cambios de signo dependiendo de los sectores de paridad, etc.

Luego se supone que hay ciertas operaciones definidas en tensores, la más importante de las cuales es contraer tensores, pero también, por ejemplo, factorizaciones ortogonales (descomposición de valores singulares), etc. Finalmente, deberían existir mapas lineales que mapeen un tensor sobre otro. Se merecen un tipo especial en el sentido de que normalmente no se desea codificarlos completamente como matriz, sino de una manera que el producto del vector de matriz se pueda calcular de manera eficiente, para su uso en métodos iterativos (Lanczos, etc.). Mis dos paquetes existentes hasta ahora (TensorOperations.jl y LinearMaps.jl) implementan esta funcionalidad para Arrays estándar, la caja de herramientas de tensor en construcción los sobrecargaría / redefiniría para la nueva jerarquía AbstractTensor.

Espero que este paquete sea lo suficientemente general como para que también sea útil para la comunidad de física y matemáticas en general. Por ejemplo, si aparece alguien que crea un paquete para trabajar con variedades, entonces podría definir un espacio vectorial TangentSpace como subespacio del InnerProductSpace abstracto, y luego puede crear inmediatamente tensores que viven en el producto tensorial de unos pocos espacios tangentes y cotangentes. De hecho, estoy pensando en dividir la parte para definir espacios vectoriales en un paquete separado, que podría convertirse en un paquete para definir estructuras / objetos matemáticos.

Finalmente, la interoperabilidad con julia estándar proviene de llamar a tensor en un Array estándar, que lo envuelve en un objeto de tipo Tensor con los índices asociados a espacios de tipo CartesianSpace . Este es el espacio vectorial real estándar R ^ n con producto euclidiano, donde no hay distinción entre índice covariante y contravariante. Creo que esto implica mejor lo que es un Julia Array estándar.

@JeffBezanson , soy ambivalente con respecto a tratar las matrices como subconjuntos de tensores. No se pierde información de esa manera, pero al mismo tiempo hay múltiples interpretaciones posibles para las matrices, y la interpretación del tensor no siempre (o incluso usualmente) tiene sentido. Considere las imágenes: una imagen se puede considerar como un campo con valores vectoriales en una variedad (típicamente 2d). Restringir ese campo a una cuadrícula rectangular le da una estructura que, naturalmente, le gustaría representar usando una matriz 3D. Sin embargo, en realidad, esto es solo un mapeo del espacio de puntos de la cuadrícula en el espacio vectorial {R, G, B}, por lo que el significado geométrico de las dos primeras dimensiones (las etiquetas xey de la cuadrícula) es diferente del significado geométrico de la tercera dimensión (que es, de hecho, un vector).

No me opongo a la sugerencia de @Jutho de dividir la mecánica del tensor en un paquete separado. Probablemente tenga razón en que la cantidad de usuarios que necesitan la mecánica completa del tensor es mucho menor que la cantidad de personas que solo quieren operaciones de matriz sencillas. La pregunta que realmente estamos tratando de hacer aquí es "¿en qué dominio debería caer el álgebra lineal?"

La maquinaria del álgebra lineal es un subconjunto suficientemente sustantivo de la maquinaria del álgebra tensorial que, al menos en mi opinión, no tiene sentido implementar el primero sin implementar también el segundo. Las operaciones como v'M se representan de forma más concisa y coherente si tenemos una noción de vectores covariantes y contravariantes, pero eso ya nos sitúa en la mayor parte del camino hacia las operaciones tensoriales generales.

Estoy de acuerdo con usted en que esto es conceptualmente similar a las operaciones con números reales que devuelven números complejos.

Considere las imágenes: una imagen se puede considerar como un campo con valores vectoriales en una variedad (típicamente 2d). Restringir ese campo a una cuadrícula rectangular le da una estructura que, naturalmente, le gustaría representar usando una matriz 3D. Sin embargo, en realidad, esto es solo un mapeo del espacio de puntos de la cuadrícula en el espacio vectorial {R, G, B}, por lo que el significado geométrico de las dos primeras dimensiones (las etiquetas xey de la cuadrícula) es diferente del significado geométrico de la tercera dimensión (que es, de hecho, un vector).

Si bien esto no aborda ni quita su mensaje general, https://github.com/timholy/Images.jl/pull/135 está trabajando para implementar esta idea para las imágenes. Espero que esto también facilite el manejo de los tensores de estructura de color , que estoy buscando usar para un proyecto.

El 23 de agosto de 2014, a las 20:36, jdbates [email protected] escribió:

@JeffBezanson , soy ambivalente con respecto a tratar las matrices como subconjuntos de tensores. No se pierde información de esa manera, pero al mismo tiempo hay múltiples interpretaciones posibles para las imágenes, y la interpretación del tensor no siempre (o incluso generalmente) tiene sentido. Considere las imágenes: una imagen se puede considerar como un campo con valores vectoriales en una variedad (típicamente 2d). Restringir ese campo a una cuadrícula rectangular le da una estructura que, naturalmente, le gustaría representar usando una matriz 3D. Sin embargo, en realidad, esto es solo un mapeo del espacio de puntos de la cuadrícula en el espacio vectorial {R, G, B}, por lo que el significado geométrico de las dos primeras dimensiones (las etiquetas xey de la cuadrícula) es diferente del significado geométrico de la tercera dimensión (que es, de hecho, un vector).

Estoy de acuerdo en que los tensores no reemplazan a las matrices. Este ejemplo anterior es de hecho una estructura matemática diferente (es decir, un conjunto de vectores o más generalmente un conjunto de tensores) cuya representación también se puede dar como una matriz multidimensional eligiendo una cuadrícula para las coordenadas múltiples y una base para la parte del espacio vectorial. Entonces, de hecho, puede tener diferentes objetos / estructuras matemáticos que están bien definidos de una manera independiente de coordenadas / independiente de la base, pero que se pueden representar (después de elegir un sistema de coordenadas o una base) como una matriz multidimensional. Por lo tanto, las matrices multidimensionales ciertamente no se limitan a representar tensores. La otra forma también falla, ya que no todos los tensores tienen una representación conveniente usando una matriz multidimensional. Ese es solo el caso cuando se usa una base particular conocida como base del producto, que se obtiene tomando el producto directo de todas las combinaciones posibles de los vectores de base individuales de los espacios vectoriales involucrados en el espacio del producto tensorial. En algunos casos, por ejemplo, cuando se utilizan tensores en un subespacio invariante de simetría del espacio del producto del tensor, dicha representación ya no es posible y es necesario definir una base diferente para el espacio completo, con respecto al cual el tensor simplemente se representa como una larga lista unidimensional de números.

No me opongo a la sugerencia de @Jutho de dividir la mecánica del tensor en un paquete separado. Probablemente tenga razón en que la cantidad de usuarios que necesitan la mecánica completa del tensor es mucho menor que la cantidad de personas que solo quieren operaciones de matriz sencillas. La pregunta que realmente estamos tratando de hacer aquí es "¿en qué dominio debería caer el álgebra lineal?"

La maquinaria del álgebra lineal es un subconjunto suficientemente sustantivo de la maquinaria del álgebra tensorial que, al menos en mi opinión, no tiene sentido implementar el primero sin implementar también el segundo. Las operaciones como v'M se representan de forma más concisa y coherente si tenemos una noción de vectores covariantes y contravariantes, pero eso ya nos sitúa en la mayor parte del camino hacia las operaciones tensoriales generales.

Estoy de acuerdo con usted en que esto es conceptualmente similar a las operaciones con números reales que devuelven números complejos.

-
Responda a este correo electrónico directamente o véalo en GitHub.

existen múltiples interpretaciones posibles para las matrices, y la interpretación del tensor no siempre (o incluso generalmente) tiene sentido. Considere las imágenes: una imagen se puede considerar como un campo con valores vectoriales en una variedad (típicamente 2d). Restringir ese campo a una cuadrícula rectangular le da una estructura que, naturalmente, le gustaría representar usando una matriz 3D. Sin embargo, en realidad, esto es solo un mapeo del espacio de puntos de la cuadrícula en el espacio vectorial {R, G, B}, por lo que el significado geométrico de las dos primeras dimensiones (las etiquetas xey de la cuadrícula) es diferente del significado geométrico de la tercera dimensión (que es, de hecho, un vector).

Era este tipo de distinción lo que intentaba capturar en la propuesta AbstractTensorArray teórica de https://github.com/JuliaLang/julia/issues/4774#issuecomment -38333295 al permitir tanto el tipo de matriz como el tensor -como dimensiones. Bajo este esquema, esperaría representar su ejemplo como

AbstractTensorArray{Uint8, 3, [false, true, false], [true, false, false]}

de modo que las dimensiones x, y y RGB estén "hacia abajo", "hacia arriba" y "neutrales", respectivamente. Las operaciones geométricas (por ejemplo, transformaciones afines) podrían manejar las dimensiones de las coordenadas de la cuadrícula en forma de tensor mientras mapean los valores RGB en forma de matriz. (Si luego desea tratar los valores RGB geométricamente, tendrá que cambiar explícitamente la máscara para ese propósito, pero supongo que (a) es menos común que se apliquen dos tipos diferentes de operaciones geométricas a diferentes subespacios de la misma tabla de datos, y (b) en esta situación, una conversión explícita probablemente _mejoraría_ la claridad del código).

No había considerado las representaciones conjugadas que menciona @Jutho , pero me parece que esta generalización podría manejarse extendiendo aún más el mismo enfoque de enmascaramiento, para espacios complejos.

La pregunta que realmente estamos tratando de hacer aquí es "¿en qué dominio debería caer el álgebra lineal?"

Una vez que se establece un diseño sobre cómo las operaciones de tipo arreglo y tipo tensor se combinan, las entidades para el álgebra lineal pueden definirse simplemente mediante casos especiales (como los alias que usé anteriormente), de modo que el usuario de álgebra lineal pura puede ignorar toda la jerarquía tensorial generalizada hasta que sea necesario (pero no tendrá que reescribir las cosas si lo es y cuándo). Entonces no vería ningún problema (excepto tal vez hinchazón) al poner todo en Base.

de modo que las dimensiones x, y y RGB estén "hacia abajo", "hacia arriba" y "neutrales", respectivamente. Las operaciones geométricas (por ejemplo, transformaciones afines) podrían manejar las dimensiones de las coordenadas de la cuadrícula en forma de tensor mientras mapean los valores RGB en forma de matriz. (Si luego desea tratar los valores RGB geométricamente, tendrá que cambiar explícitamente la máscara para ese propósito, pero supongo que (a) es menos común que se apliquen dos tipos diferentes de operaciones geométricas a diferentes subespacios de la misma tabla de datos, y (b) en esta situación, una conversión explícita probablemente mejoraría la claridad del código).

Creo que estás mezclando algo aquí. En la discusión anterior, en realidad se explicó que las coordenadas xey no llevaban la interpretación del espacio vectorial, ya que pueden corresponder a coordenadas en una variedad curva arbitraria, no necesariamente un espacio plano. Fue la dimensión RGB a la que se le dio la interpretación vectorial, aunque esta podría no ser la mejor opción, como creo recordar (no tengo un fondo decente en el procesamiento de imágenes) que el espacio de color también es bastante curvo. Además, incluso para el caso en el que el dominio (xey) forma un espacio vectorial, ¿por qué xey estarían arriba y abajo, o esto fue solo un ejemplo de su notación?

De todos modos, también comencé con TensorToolbox.jl al denotar índices covariantes y contravariantes como algún tipo de parámetro o máscara, pero esto pronto se convirtió en una completa pesadilla, por lo que cambié a una representación donde cada tensor es un elemento de algún espacio vectorial , y para realizar operaciones, uno debe verificar que los espacios coincidan, al igual que debe verificar que los tamaños coincidan cuando se realizan operaciones con arreglos.

Las coordenadas xey no llevaron la interpretación del espacio vectorial

Lo siento, leí demasiado "cuadrícula rectangular" --- supongo que @jdbates quiso decir precisamente lo que dijo. ¿Pero no estamos hablando simplemente de reemplazar los productos punto con productos internos generalizados? (Perdóname si no entiendo bien, paso casi todo mi tiempo en el espacio euclidiano :-)

cada tensor es un elemento de algún espacio vectorial

Parece una buena idea --- Me interesaría ver algunos ejemplos de cómo funciona para el usuario (no llegué muy lejos leyendo el código).

Tengo una nueva propuesta para este tema.


(1) Rebanado estilo APL.

size(A[i_1, ..., i_n]) == tuple(size(i_1)..., ..., size(i_n)...)

En particular, esto significa que los "segmentos singleton", es decir, los segmentos en los que el índice es escalar o de dimensión cero, siempre se eliminan y M[1,:] y M[:,1] son ambos vectores, en lugar de que uno sea un vector. mientras que el otro es una matriz de filas, o cualquier otra distinción similar.


(2) Introduzca los tipos de envoltura Transpose y ConjTranspose para vectores y matrices. En otras palabras, algo como esto:

immutable Transpose{T,n,A<:AbstractArray} <: AbstractArray{T,n}
    array::A
end
Transpose{T,n}(a::AbstractArray{T,n}) = Transpose{T,n,typeof(a)}(a)

y todos los métodos apropiados para que estos funcionen como deberían para vectores y matrices. Es posible que queramos limitarlo a trabajar solo para vectores y matrices, ya que no está claro qué debería significar una transposición general para dimensiones arbitrarias (aunque es tentador invertir las dimensiones). Cuando escribe a' obtiene ConjTranspose(a) e igualmente v.' produce Transpose(a) .


(3) Definir varios métodos especializados para (conjugar) vectores y matrices transpuestos, tales como:

*(v::Transpose{T,1}, w::AbstractVector) = dot(v.array,w)
*(v::AbstractVector, w::Transpose{T,1}) = [ v[i]*w[j] for i=1:length(v), j=1:length(w) ]

etc., incluida la sustitución de todas las horribles funciones At_mul_B y el análisis especial con la construcción de transposición perezosa (conjugada) seguida de envío en los tipos Transpose y ConjTranspose .


(4) Restrinja las operaciones de difusión a los casos en que los argumentos sean escalares o matrices con el mismo número de dimensiones. Por lo tanto, lo siguiente, que actualmente funciona como se muestra, fallará:

julia> M = rand(3,4);

julia> M./M[1,:]
3x4 Array{Float64,2}:
 1.0       1.0       1.0      1.0
 0.516884  0.675712  2.11216  9.0797
 1.00641   0.726229  2.48336  4.38751

julia> M./M[:,1]
3x4 Array{Float64,2}:
 1.0  0.891557  0.561464  0.103968
 1.0  1.16552   2.29433   1.82633
 1.0  0.643353  1.38544   0.453257

En su lugar, tendrá que hacer algo como esto:

julia> M./M[[1],:]
3x4 Array{Float64,2}:
 1.0       1.0       1.0      1.0
 0.516884  0.675712  2.11216  9.0797
 1.00641   0.726229  2.48336  4.38751

julia> M./M[:,[1]]
3x4 Array{Float64,2}:
 1.0  0.891557  0.561464  0.103968
 1.0  1.16552   2.29433   1.82633
 1.0  0.643353  1.38544   0.453257

Creo que esta propuesta resuelve todos los principales problemas que tenemos actualmente:

  1. Comportamiento de corte simétrico: las dimensiones finales ya no son especiales.
  2. v'' === v .
  3. v' == v .
  4. v'w es el producto escalar de v y w ; en particular, es un vector escalar, no un elemento.
  5. v*w' es el producto externo de v y w .
  6. M*v es un vector.
  7. M*v' es un error.
  8. v'*M es un vector transpuesto.
  9. v*M es un error.
  10. At_mul_B operadores

: +1: a todo. Trabajé un poco en 2 y 3 en el n. ° 6837, pero nunca lo terminé. @simonbyrne también lo

+1 también. Parece que ofrecería un comportamiento bastante consistente en todo el lugar.

La única parte realmente disruptiva de esta propuesta sería en realidad que M[1,:] es un vector implícitamente vertical en lugar de una matriz de filas explícitamente horizontal. De lo contrario, en realidad es un conjunto de cambios bastante fluido y no disruptivo (uno espera). La principal epifanía (para mí) fue que el comportamiento de corte de APL podría combinarse con transposiciones perezosas. Si logramos la aceptación, podemos idear un plan y dividir el trabajo. Realmente espero que las transposiciones perezosas y las funciones por etapas permitan cierta reducción y simplificación de código.

¡Sí por favor! La transposición de tensor probablemente debería permitir cualquier permutación definida por el usuario, con la inversión de la atenuación como opción predeterminada.

La transposición de tensor probablemente debería permitir cualquier permutación definida por el usuario, con la inversión de la atenuación como opción predeterminada.

Eso parece complicar un poco el tipo, tal vez podamos tener un tipo PermuteDims que permita la permutación arbitraria de la dimensión perezosa.

@Stefan : Parece una idea bastante buena para calcular el vector y 2-dim
álgebra. Solo algunos desafíos:

  1. Con respecto a los casos de matrices de varias dimensiones: para una matriz A con dimensión
    (i_1, i_2, ..., i_n), si se quiere que la transposición se aplique al [i_2, i_3]
    dimensiones, o incluso hash, en las dimensiones [i_2, i_4]. Puedes hacerlo en
    la nueva definición de transposición?
  2. Con respecto a la dimensión singleton: es posible que un segmento singleton sea
    dejado intencionalmente. ¿Debería Julia mantener esta dimensión singleton después de la
    ¿cálculo? Por ejemplo, si uno define un vector como una matriz V en el
    dimensión de (2,1), y quiere multiplicar la transposición con una matriz A en
    dimensión (2,3,4). ¿Puede obtener el resultado de v '* A en la dimensión de
    (1,3,4)?

El jueves 16 de octubre de 2014 a las 2:31 p.m., Stefan Karpinski [email protected]
escribió:

El único inconveniente sería que M [1 ,:] es un vector (vertical)
en lugar de una matriz de filas. De lo contrario, es bastante sencillo
conjunto de cambios no disruptivos (uno espera). La principal epifanía (para mí) fue
que el comportamiento de corte de APL podría combinarse con transposiciones perezosas. Si conseguimos
compra, podemos elaborar un plan y dividir el trabajo. realmente espero
que las transposiciones perezosas y las funciones por etapas permiten cierta reducción de código y
simplificación.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -59425385.

Re 2 y 3: después de haberlo intentado, llegué a la conclusión de que la transposición vectorial NO debe ser un subtipo de AbstractVector , de lo contrario todo se complica (ver discusión en # 6837). Creo que la forma más sana de avanzar es con Transpose{T,A} <: AbstractMatrix{T} , y un tipo Covector separado (+ Conjugate variantes).

El otro problema significativo con el que me encontré es que a menudo desea enviar en un tipo de matriz específico, su transposición o su transposición conjugada. Desafortunadamente, no pude encontrar una manera de expresar esto a través de la maquinaria de tipo existente (vea esta discusión de la lista de correo ). Sin esto, me temo que tendremos una gran cantidad de @eval -ing en 3x3 posibles combinaciones de argumentos.

@simonbyrne , confío en su experiencia con la implementación. ¿El resto parece razonable?

He señalado (en foros menos públicos, por lo que probablemente merezca una breve mención aquí) que una alternativa potencial es manejar todas las configuraciones _ internamente_, expandiendo los tipos de índices que pueden usar SubArrays. En particular, se podría tener un tipo de "rango transpuesto" que conferiría una forma transpuesta al SubArray, incluso cuando el arreglo padre sea Vector . (Consulte https://github.com/JuliaLang/julia/blob/d4cab1dd127a6e13deae5652872365653a5f4010/base/subarray.jl#L5-L9 si no está familiarizado con cómo se implementan / pueden implementarse los SubArrays).

No estoy seguro de si esta estrategia alternativa hace la vida más fácil o más difícil. Reduce el número de tipos externos, lo que puede significar que se necesitan menos métodos. (Como alguien que todavía está completando los métodos que faltan debido a la transición Color en Images , esto parece algo bueno). Por otro lado, en ausencia de un envío triangular conveniente, podría hacen que sea algo más incómodo escribir métodos selectivos, lo que podría exacerbar los problemas planteados por @simonbyrne.

Cualquier información será bienvenida.

Aparte de esos detalles, me gusta la forma de la propuesta de @StefanKarpinski . No estoy casado con la indexación de estilo APL, pero en general, sospecho que es una mejor opción que las reglas derivadas de Matlab que tenemos ahora.

Dos pensamientos:

  • Si indexar como A[[2], :] vuelve idiomático, parece un poco derrochador tener que crear un vector solo para ajustar el índice único 2 . ¿Deberíamos considerar permitir A[(2,), :] por lo mismo o algo similar? Supongo que un rango de un solo elemento está bien, pero sería bueno tener una sintaxis que sea casi tan conveniente como [2] .
  • Si necesitamos un número coincidente de dimensiones para realizar la transmisión, debería haber una forma sencilla de agregar dimensiones singleton a una matriz, tal vez algo como la indexación newaxis numpy.

Estaba pensando en proponer que indexar con punto y coma, a la A[2;:] , podría ser un modo de indexación diferente donde el resultado siempre tiene el mismo número de dimensiones que A , es decir, no suelte singletons e indexe con cualquier cosa que tenga más de uno es un error. Decidió dejar eso fuera de la propuesta principal por simplicidad, pero algo así parece algo bueno para tener.

Puedo ver las preocupaciones expresadas por @simonbyrne . Sin embargo, en principio, un covector es también un vector que vive en un espacio vectorial diferente, es decir, el espacio dual. Así que hacer que el tipo Transpose o Covector no sea un subtipo de AbstractArray también se siente algo desagradable. Una posible resolución, que sería un cambio importante y probablemente no se considerará (pero quería mencionarlo de todos modos) es darle a toda la familia AbstractArray un parámetro de tipo adicional trans , que podría tener valores :N , :T o :C . Para todos los métodos que simplemente asumen que un vector es una lista unidimensional de números, no necesitarían distinguir entre diferentes valores de este parámetro final, por lo que las definiciones de método correspondientes pueden permanecer como están ahora.

Para matrices N-dimensionales con N> 2, existen varias opciones. O transpose da un error y es imposible crear realmente un objeto de tipo AbstractArray{3,Float64,trans} donde trans!=:N o, alternativamente, :T solo significa fila-mayor y transpose de una matriz general tiene el efecto de invertir todas las dimensiones. Creo que esta última es también la convención aceptada por aquellas personas que usan la notación gráfica de Penrose (ver http://en.wikipedia.org/wiki/Penrose_graphical_notation aunque la transposición no se explica allí, pero también ver el libro citado de Cvitanović).

Realmente no veo el papel de las permutaciones de índice arbitrarias soportadas por transpose , hay permutedims para eso, y tal vez algún enfoque perezoso usando SubArrays renovados. Además, la principal motivación para este problema es simplificar el zoológico A_mul_B, y las contracciones de tensor de orden superior no son (ni deberían ser) compatibles con la multiplicación normal de todos modos.

Estoy seguro de que hay algunos problemas nuevos relacionados con este enfoque en los que aún no he pensado.

Creo que encontré una solución razonable al problema de envío aquí .

La propuesta de @Jutho parece interesante y creo que vale la pena explorarla. Desafortunadamente, la única forma real de evaluar estas cosas es intentar implementarlas.

@toivoh ,

  • A[2:2,:] también conservará la dimensión, y eso no requiere asignación ni ninguna sintaxis nueva.
  • Algo como newaxis parece eminentemente factible. De hecho, con la arquitectura en # 8501 parece posible crear transmisión directamente por indexación: tenga un tipo de índice que siempre se resuelva en 1 sin importar el valor que el usuario ingrese en esa ranura.

El problema con 2:2 es la repetición si hay una expresión larga para el índice en lugar de solo 2 . Pero, por supuesto, siempre puede definir su propia función para crear un rango a partir de un índice.

Muy buena propuesta: +1 :.

Recuérdame por qué queremos v' == v ?

Realmente no necesitamos eso, pero es algo agradable ya que el dual de un espacio vectorial (de dimensión finita) es isomórfico a él.

O más fuertemente, dado que las matrices de Julia no distinguen entre índices covariantes y contravariantes, solo tiene sentido pensar que estos son vectores en un espacio cartesiano (métrica euclidiana = matriz de identidad = delta de kronecker), donde de hecho el espacio dual es naturalmente isomorfo.

No estoy tan seguro de que queramos v '== v, pero creo que eso es bastante ortogonal a
el resto. ¿Queremos una matriz de columna y un vector para comparar iguales si
tienen elementos iguales?

En realidad, ese es un problema diferente, ya que tienen diferentes números de dimensiones.

En particular, esta propuesta elimina efectivamente la identificación entre un vector y una matriz de columna, porque si corta una matriz horizontal o verticalmente, obtiene un vector de cualquier manera. Anteriormente, podía ignorar las dimensiones de singleton finales, o pretender que había más de las que realmente había. Ya no será una buena idea hacer eso porque un vector puede provenir de cualquier porción de una matriz.

¿Tendría sentido convert algo de 1-d a 2-d agregando una dimensión singleton final?

Con esta propuesta, creo que quizás ya no sea una buena idea. Pero tal vez dado que los vectores todavía se comportan como columnas mientras que los covectores se comportan como filas.

Una cosa que noté en el # 8416 es que sparsevec está desordenadamente falsificado como una matriz CSC de una sola columna en este momento. Sparse debería poder encajar bastante bien en esto una vez que se implemente un tipo de vector disperso 1-d adecuado (que se consideraría el caso útil más simple de un tipo genérico de Nd COO, solo necesita ser escrito).

Simplemente asimilando todo esto. ¿Entonces lo siguiente no funcionaría?

A [1 ,:] * A * A [:, 1] # fila de una columna Matriz * Matriz * de una matriz ???

Tu escribiste

v'w es el producto escalar de v y w; en particular, es un vector escalar, no un elemento.

¿También v '* w es un escalar?

Me gusta la idea de que el punto (x, y) tome dos elementos cuyas formas sean (1, ..., 1, m, 1, ..., 1) y
devolver el producto escalar pase lo que pase. Pero no quiero que x * y dé un punto (x, y) en este sentido
a menos que x sea un covector e y sea un vector.

No estoy seguro de si esta es una idea tan interesante, pero tal vez estaría bien si
A [:, 1,1] era un vector y A [1,:, 1] o A [:, 1,:] eran covectors.
Se siente mejor seguir una dimensión para el vector: la ranura, en la que
se les permite contraer el tensor, siendo el álgebra lineal estándar
1 (vectores de fila) y 2 vectores de columna.

En mi opinión, los dos principales desafíos que habíamos abordado anteriormente en este tema eran:

(A) cómo distinguir la semántica de tensores (para contracciones) y la semántica de matrices (para transmisión) cuando se opera con datos multidimensionales;
(B) cómo integrar el álgebra lineal obvia y conveniente dentro de un marco coherente que se generaliza a dimensiones superiores.

No tengo claro cómo esta propuesta aborda estos temas. Por lo que puedo decir, lograr (A) todavía requiere manipulaciones ad-hoc por parte del usuario (como ocurre con la funcionalidad actual); y para abordar (B) el uso de envoltorios perezosos requeriría algo como las extensiones SubArray sugeridas por @timholy , momento en el que se convierte en una versión perezosa del enfoque de enmascaramiento discutido hace algún tiempo. Me imagino proporcionar soporte adicional para (A) usando algún mecanismo perezoso similar (como un tipo de envoltorio List ), pero en todos estos casos me parece que la pereza debería ser una estrategia opcional.

No sé cuántos comparten la opinión de @Jutho de que "las contracciones de los tensores de orden superior no son (y no deberían ser) compatibles con la multiplicación normal de todos modos", pero no podría estar más en desacuerdo: solo hago lo que considero ingeniería ordinaria matemáticas, y las necesito todo el tiempo. Si bien los lenguajes actuales como Mathematica y NumPy tienen sus limitaciones de diseño en este sentido (como lo mencioné anteriormente), ¡al menos son compatibles! Por ejemplo, tan pronto como desee utilizar el gradiente de un campo vectorial en un método numérico simple, necesitará contracciones tensoriales de orden superior.

Cuando dices, "... tienen sus limitaciones de diseño en este sentido (como lo mencioné anteriormente), al menos son compatibles", ¿estás hablando de una funcionalidad faltante o algo fundamental sobre los vectores y transposiciones que no se pueden abordar en un nivel superior, o agregando funciones?

¿Hay algo de esta propuesta en conflicto con mejorar sus puntos (A) y (B)?

Realmente no veo cómo las contracciones tensoriales son compatibles con el operador de multiplicación estándar de matlabs *, o mediante cualquier otra función incorporada de matlab. Numpy tiene una función incorporada (olvidé el nombre) pero también es bastante limitada por lo que recuerdo.

Yo también necesito las contracciones tensoriales en su forma más general todo el tiempo, por eso exactamente sé que especificar la contracción tensorial más general, y mucho menos implementarla de manera eficiente, no es del todo sencillo. Es por eso que argumentó que es necesario que haya funciones especiales para esto, en lugar de intentar meter algo de funcionalidad a medias o más bien específica en los operadores estándar en la base de Julia, que no cubre la mitad de los casos de uso. Pero estoy feliz de cambiar mi opinión, por ejemplo, ¿si hay una contracción 'estándar' que es mucho más importante / útil que cualquier otra? Pero esto podría depender mucho del dominio y, por lo tanto, no es adecuado como regla general para su adopción en Julia Base.

Op 19-oct.-2014 om 22:52 heeft thomasmcoffee [email protected] het volgende geschreven:

En mi opinión, los dos principales desafíos que habíamos abordado anteriormente en este tema eran:

(A) cómo distinguir la semántica de tensores (para contracciones) y la semántica de matrices (para transmisión) cuando se opera con datos multidimensionales;
(B) cómo integrar el álgebra lineal obvia y conveniente dentro de un marco coherente que se generaliza a dimensiones superiores.

No tengo claro cómo esta propuesta aborda estos temas. Por lo que puedo decir, lograr (A) todavía requiere manipulaciones ad-hoc por parte del usuario (como ocurre con la funcionalidad actual); y para abordar (B) el uso de envoltorios perezosos requeriría algo como las extensiones SubArray sugeridas por @timholy , momento en el que se convierte en una versión perezosa del enfoque de enmascaramiento discutido hace algún tiempo. Me imagino proporcionar soporte adicional para (A) usando algún mecanismo perezoso similar (como un tipo de contenedor de lista), pero en todos estos casos me parece que la pereza debería ser una estrategia opcional.

No sé cuántos comparten la opinión de @Jutho de que "las contracciones de los tensores de orden superior no son (y no deberían ser) compatibles con la multiplicación normal de todos modos", pero no podría estar más en desacuerdo: solo hago lo que considero ingeniería ordinaria matemáticas, y las necesito todo el tiempo. Si bien los lenguajes actuales como Mathematica y NumPy tienen sus limitaciones de diseño en este sentido (como lo mencioné anteriormente), ¡al menos son compatibles! Por ejemplo, tan pronto como desee utilizar el gradiente de un campo vectorial en un método numérico simple, necesitará contracciones tensoriales de orden superior.

-
Responda a este correo electrónico directamente o véalo en GitHub.

aquí hay una contracción sobre el último índice de A y el primer índice de B
algo así como el punto de Mathica

function contract(A,B)
   s=size(A)
   t=size(B)
   reshape(reshape(A, prod(s[1:end-1]), s[end]) *  reshape(B,t[1],prod(t[2:end])) , [s[1:end-1]... t[2:end]...]...)
end

Siempre he podido hacer contracciones generales con remodelaciones, permutaciones y quizás complejas
se conjuga cuando se necesita más o menos como el anterior

no estoy seguro de cuál es realmente el gran problema con los tensores, ¿por qué no podemos simplemente implementar algunos de estos
funciones?

Sí exactamente. En este número, lo único que queremos establecer para seguir adelante es

  1. ¿Qué dimensiones colocar en la indexación? El "estilo APL" parece indiscutible.
  2. ¿Qué da vector' ?

Para las contracciones tensoriales, con tipos apropiados y funciones por etapas, creo que podríamos obtener implementaciones de alto rendimiento.

Mi sensación es que los tensores se cuidarán solos y tenemos que estar seguros
que los usuarios de álgebra lineal no se frustran.

Mi mayor preocupación es que

(tomar una fila de una matriz 2d) * (matriz 2d) * (tomar una columna de una matriz 2d)

que es una operación común, todavía no funcionará a menos que tomemos
(tomar una fila) con covector o quizás mejor aún
etiquételo con un índice general de ranuras.

@JeffBezanson , cuando digo que estas operaciones son compatibles, me refiero a que los tipos de datos incorporados y las funciones están diseñados específicamente con ellos en mente, por ejemplo, como la función Dot Mathematica. Por lo tanto, para el usuario, existe una forma incorporada, documentada y / o obvia de hacer ciertas cosas. Para cualquier diseño, es posible lograr soporte para cualquier cosa agregando funciones, tal como ocurre con la implementación actual; por lo que no es una cuestión de conflicto técnico, es una cuestión de diseño.

@Jutho , no uso mucho MATLAB, así que no puedo comentar. Estoy de acuerdo en que el diseño de NumPy es menos coherente que el de Mathematica (como mencioné anteriormente), pero también admite una gama más rica de comportamientos. Estoy de acuerdo en que el álgebra lineal básica debería dejar la maquinaria tensorial general invisible para los usuarios que no la necesitan, pero debido a las fabulosas características del lenguaje de Julia, no parece necesario introducir implementaciones divergentes para ellos, ya que tanto NumPy como Mathematica lo han hecho. se ha visto obligado a hacer hasta cierto punto. Me pareció que este problema se trataba, al menos en parte, de encontrar el sistema unificado correcto para ambos, para revelar qué especializaciones deberían usarse para los casos comunes de álgebra lineal: por ejemplo, qué hacer con vector' .

A [1 ,:] * A * A [:, 1] # fila de una columna Matriz * Matriz * de una matriz ???

Correcto, tendría que escribir A[1,:]' * A * A[:,1] .

¿También v '* w es un escalar?

Sí, v'w son lo mismo. Una de las cosas buenas de esta propuesta es que elimina por completo los trucos sintácticos baratos.

No estoy seguro de si esta es una idea tan interesante ...

No creo que lo sea. Uno de los objetivos de esta propuesta es hacer que las reglas de segmentación e indexación sean simétricas y esto haría que uno de los índices sea especial, lo que me parece que anula todo el propósito. Si el corte va a ser asimétrico, también podríamos mantener el comportamiento actual.

@thomasmcoffee Tendrás que ser más específico. Por supuesto, todo el mundo quiere que las cosas sean coherentes, documentadas, obvias, etc. La pregunta es, ¿la propuesta sobre la mesa cumple esos objetivos? Tal vez la propuesta actual no afecte esos objetivos en absoluto, lo cual está bien, entonces, mientras conduzca a una mejora en otros lugares, todavía tenemos una mejora neta.

Así que déjame ver si lo entiendo

Si A no es cuadrado

| | Actual | Propuesto | MATLAB |
| --- | --- | --- | --- |
| A * A [1 ,:] | No | Si | No |
| A * A [1 ,:] '| Si | No | Si |
| A [:, 1] A | No |
A [:, 1] ' A | Si | Si | Si |

y si A es cuadrado

| | Actual | Propuesto | MATLAB |
| --- | --- | --- | --- |
| A * A [:, 1] | Si | Si | Si |
| A * A [:, 1] '| No | No | No |
| A [1 ,:] A | No |
A [1 ,:] ' A | No | Si | No |

Juro que acabo de publicar una respuesta a esto, pero de alguna manera desapareció en el éter. Todo esto es correcto. En la disposición actual, debe considerar si está tomando un segmento de fila o un segmento de columna, así como si está multiplicando a la izquierda o a la derecha al decidir si transponer o no (las columnas se transponen a la izquierda, las filas se transpuesto a la derecha). En la propuesta, solo considera de qué lado está multiplicando: siempre transpone a la izquierda, nunca a la derecha.

Estaría bien si

dot(x,y) y dot(x.',y) y dot(x,y.') y dot(x.',y.') dan todos el mismo escalar?

es decir, Σᵢ conj (xᵢ) * yᵢ

de esta manera uno puede hacer dot (x, A * y) sin pensar demasiado

Esos ejemplos de @alanedelman se sienten un poco retrógrados en los lugares donde no debería, debido a la indexación APL. Tal vez eso sea suficiente motivación para tener una variante de indexación que preserve la dimensión, como creo que se ha discutido (por ejemplo, A[i; :] ?)

Pero entonces querría que eso le diera un covector en el caso anterior.

@alanedelman , no veo ninguna razón por la que esos métodos de dot no deberían existir y dar el mismo resultado.

Siempre he podido hacer contracciones generales con remodelaciones, permutaciones y quizás complejas
se conjuga cuando se necesita más o menos como el anterior

Así es exactamente como se implementa en la función tensorcontract en TensorOperations.jl, si elige el método: BLAS, que sin duda es el más rápido para tensores grandes. También escribí una implementación nativa de julia, usando la funcionalidad Cartesian.jl (y con suerte algún día usando funciones por etapas) que elimina la necesidad de permutedims (asignación de memoria adicional) y es más rápido para tensores más pequeños.

Solo estaba respondiendo a la afirmación falsa de que matlab proporciona una funcionalidad incorporada para esto que Julia no lo hace. Tanto el remodelado como el permutado están disponibles en Julia. De hecho, Numpy tiene la función tensordot que hace exactamente esto, pero no le permite especificar el orden de índice del tensor de salida, por lo que aún necesita un permutedims después si tenía un orden de salida específico en mente.

Pero esto se está alejando demasiado del tema actual, que de hecho es obtener un comportamiento consistente para el álgebra de vectores y matrices.

+1 por la propuesta de Stefan. Parece dar una semántica extremadamente clara pero suficientemente flexible. Como usuario de álgebra lineal, incluso uno acostumbrado a la sintaxis de estilo MATLAB, las reglas me parecen lo suficientemente simples de usar.

Estoy un poco confundido acerca de lo que se supone que significa ' en general. Si v es un vector, v' es un transpose . Si a es una matriz 2d, a' ahora también sería transpose . Ambos parecen estar definidos con el interés de poder formar fácilmente b' * a contratando la primera dimensión de b con la primera dimensión de a .

Parece que no hay consenso sobre una definición de a' cuando la dimensión de a es> 2. No he escuchado a nadie proponer nada más que revertir las dimensiones, y esto coincide con b' * a contratando la primera dimensión de b con la primera dimensión de a .

Creo que sería bueno si pudiéramos decir lo que hace ' manera sucinta sin hacer referencia al tamaño de la cosa en la que está operando.

Parece razonable tener otra función de contracción disponible en Base para situaciones más generales, por ejemplo, contract(a, b, 2, 3) para contraer la 2ª dimensión de a con la 3ª de b .

Por cierto, dot(a,b) == a'*b cuando a y b son vectores, pero dot(a,b) actualmente no está definido para matrices. ¿Podríamos tener dot(a,b) = trace(a'*b) ?

@madeleineudell : Estoy un poco confundido acerca de lo que se supone que significa en general.

Comparto esta preocupación. Básicamente, puede tomar las propiedades 2-5, 7, 8 y 10 en mi propuesta como las características definitorias. Es decir, quieres que esto se mantenga:

  • v' es unidimensional pero no un vector
  • v'' === v
  • v' == v
  • v'w es un escalar
  • v*w' es una matriz
  • v'*M es lo mismo que v'
  • M' es bidimensional pero no una matriz, tal vez una vista de matriz

En particular, no hay restricciones sobre lo que significa o hace para matrices de dimensiones superiores. Sería bueno contar con una teoría general de qué son los covectors que abarquen dimensionalidades más altas, pero no estoy convencido de que realmente se pueda hacer sin complicar demasiado las cosas, por ejemplo, teniendo dimensiones arriba / abajo o haciendo que cada dimensión esté etiquetada con un índice.

Al menos está claro que la regla general para ' no es que invertiría las dimensiones, ya que eso no es lo que haría con los vectores y covectores.

La única regla simple en la que puedo pensar que captura el comportamiento anterior para ambos vectores, covectores y matrices es permutar las dos primeras dimensiones (un vector tiene una segunda dimensión ausente y un covector tiene una primera ausente y una segunda presente).

El 20 de octubre de 2014, a las 09:05, toivoh [email protected] escribió:

Al menos está claro que la regla general para 'no es que invertiría las dimensiones, ya que eso no es lo que haría con los vectores y covectores.

La única regla simple en la que puedo pensar que captura el comportamiento anterior para ambos vectores, covectores y matrices es permutar las dos primeras dimensiones (un vector tiene una segunda dimensión ausente y un covector tiene una primera ausente y una segunda presente).

Yo diría que esto no es cierto si quieres pensar en tensores generales. Podría ser cierto si solo piensa en las matrices y los vectores como algunos bloques con números, y piensa en v como una columna y v 'como una fila.

Sin embargo, si piensa en v como un elemento de un espacio vectorial, y w = v 'como una forma de mapear v con un elemento w del espacio dual V_, entonces w sigue siendo un vector, es decir, un objeto unidimensional. En general, se necesita una métrica para definir este mapeo de V a V_, es decir, w_i = g_ {i, j} v ^ j. Ahora bien, si V es un espacio cartesiano, es decir, R ^ n con la métrica euclidiana estándar, entonces V * es naturalmente isomorfo a V. Esto significa que no hay noción de índices superiores o inferiores (covariantes o contravariantes) y, por lo tanto, w_i = v_i = v ^ yo = w ^ yo. Yo diría que este es el caso que se usa en la mayoría de la programación, es decir, el caso que necesita ser compatible con Julia Base. (Para un espacio vectorial complejo, V * es naturalmente isomorfo a Vbar, el espacio vectorial conjugado y, por lo tanto, el mapeo natural es w_i = conj (v ^ i)).

La convención para denotar vectores como columnas con números, vectores duales como filas con números y, por lo tanto, matrices (que son elementos de V \ otimes V_) como bloques con números es extremadamente conveniente, pero se detiene tan pronto como también desee considerar dimensiones más altas. matrices, es decir, elementos de espacios de producto tensorial de orden superior. En ese caso, un 'vector de fila', es decir, un objeto bidimensional donde la primera dimensión tiene tamaño 1, para decirlo en terminología matlab / julia, es algún elemento de un espacio de producto tensorial V1 \ a veces V2, donde V1 simplemente pasa a ser R (o algún otro espacio unidimensional). Estoy de acuerdo en que esto podría no ser lo que desea como comportamiento predeterminado, pero preferiría que uno simplemente no intente definir la transposición para una matriz general y hacer referencia a permutedims, como lo hace matlab. No tiene sentido invertir las dos primeras dimensiones de un tensor general como convención predeterminada. La transposición de un elemento de algún espacio de producto tensorial de orden superior V1 \ a veces V2 \ a veces ... \ veces Vn no tiene una definición única. La convención para invertir todas las dimensiones simplemente se deriva de una conveniente representación gráfica de Penrose, como se mencionó anteriormente. Además, este es el que asigna el espacio de la matriz (V \ otimes V_) a sí mismo (V * \ otimes V * = V \ otimes V ).

Puedo ver dos caminos a seguir:
1) Hacer que el operador de conveniencia '(y tal vez incluso *) funcione con matrices arbitrarias de orden superior usando alguna convención elegida, dentro de la configuración de tensores cartesianos (es decir, sin distinción entre índices superiores o inferiores). Esto podría traer algunos resultados sorprendentes para casos de uso típicos.

2) Restringir 'y * a vectores y matrices. Error en matrices de orden superior. Creo que este es el enfoque más popular (por ejemplo, Matlab, etc.).

Esta publicación es un poco como tomar el otro lado de la posición que tomé el año pasado, para

explore la ambivalencia de ambos lados.

Espero que esté bien.
Finalmente me di cuenta de que hay una lógica en el enfoque actual, pero nunca se articuló.
Se parecía tanto a un truco que simplemente me molestó. De alguna manera ahora que entiendo
me gusta más.

En el enfoque actual, todas las matrices son de dimensión infinita con unos descartados implícitos.
El operador de apóstrofo 'podría haber significado intercambiar las dos primeras dimensiones,
pero de hecho, tal como existe hoy, significa

ndim (A) <= 2? interchange_first_two_dims: no_op

Lo siento si todos los demás vieron eso y yo me lo perdí. Mi mente estaba estancada
con la idea de que los vectores eran unidimensionales, no infinitos, y
por lo tanto, una transposición debe invertir dimensiones, por lo tanto ser un no_op.

Estoy bien con el apóstrofo haciendo esto, o el apóstrofo siempre intercambiando
las dos primeras dimensiones, no creo que me importe. Creo que el apóstrofe existe
para álgebra lineal y no multilineal.

Jugué con si apóstrofe-estrella ("'*") (¡si es posible! O algo más si es imposible)
debería significar contraer la última dimensión a la primera dimensión (como el punto de Mathematica)
y tienen indexación apl y no covectors. Parte de mi cerebro todavía piensa que vale la pena explorarlo
pero a medida que me despierto, este es el enfoque actual que parece cada vez mejor.
(Veamos como me siento hoy mas tarde)

No me arrepiento de haber comenzado este hilo el año pasado, pero me pregunto de nuevo qué casos
realmente molesta a la gente hoy ... ¿podemos obtener una lista de ejemplos? ... y si
arrojar luz sobre estos casos es mejor que cambiar lo que hacemos.

He leído todo este hilo y veo muchos principios, lo cual es bueno,
pero no suficientes casos de uso para pensar en las cosas.

Puedo decir que a menudo me ha molestado

[1 2 3] # ndims == 2
[1,2,3] # ndims == 1
[1; 2; 3] # ndims == 1

principalmente porque parece que no puedo recordarlo.

Solo un experimento mental. ¿Qué funcionalidad se perdería si redefinimos * para que sirva como un operador de contracción similar a lo que @alanedelman tiene en mente para '* ? ¿No eliminaría en absoluto la necesidad de ' en operaciones algebraicas lineales (además de funcionar como una transposición para matrices)? Solo puedo ver que nos despojaría del producto externo w*v' , que podría reemplazarse fácilmente por outer(w,v) .

EDITAR: Suponiendo corte APL. Por ejemplo, A[1,:]*A*A[:,1] tendría el resultado esperado sin la necesidad de transponer el primer operando con ' .

Yo también pensé en esto. Creo que v * w ser un producto escalado parece una sobrecarga de esteroides
que puede ser propenso a errores.
Este es un lugar donde el punto de mathica no parece tan malo.

Entonces, para resumir, un contrato de último a primero parece razonable, pero
si podría ser apóstrofe-estrella o podría ser * o debería ser un punto
tiene problemas.

Algo sin relación pero no completamente sin relación ...
Me gustaría señalar que el punto-estrella que todo el mundo lee como punto-estrella originalmente (me dijeron) era
pretendía ser una estrella puntual porque el operador POINTWISE era lo que
se entiende

Puedo decir que a menudo me ha molestado

[1 2 3] # ndims == 2
[1,2,3] # ndims == 1
[1; 2; 3] # ndims == 1

principalmente porque parece que no puedo recordarlo.

Siempre pensé "¿por qué no usamos solo , ?"

¿Qué funcionalidad se perdería si redefinimos * para que sirva como un operador de contracción similar a lo que @alanedelman tiene en mente para '*?

Perderíamos la asociatividad: por ejemplo, (M*v)*v daría el actual dot(v,M*v) (un escalar), mientras que M*(v*v) daría M.*dot(v,v) (una matriz).

¿Por qué no hacemos que dot el operador de contracción (que de todos modos no es asociativo)? También podríamos definir contracciones de orden superior, por ejemplo, ddot(A::Matrix,B::Matrix) == A⋅⋅B == trace(A'*B) .

Entonces, ¿entiendo correctamente que el único propósito de introducir transposiciones vectoriales es salvarnos de la no asociatividad de * ? La ambigüedad del ejemplo de @alanedelman podría M*v*v' vs M*v'*v ) pero lo mismo se puede hacer con paréntesis ( M*(v*v) vs (M*v)*v ) sin toda la molestia de implementar la transposición para vectores (como @Jutho ya señaló, implementar la transposición para tensores de orden superior no tiene sentido matemático de todos modos). Para mí, la pregunta es qué notación es más legible / concisa / elegante / pura.

Actualmente, M*(v*v) y (M*v)*v se analizan actualmente como

*(M, v, v)

para permitir un producto matricial que optimice el orden de multiplicación en función de los tamaños de los argumentos, por lo que el analizador también debería cambiarse.

Pero, al menos personalmente, creo que la asociatividad es muy importante. Tienes que traducir entre matemáticas y la mayoría de los lenguajes de programación; para Julia, todavía espero que no tengas que traducir mucho.

(como @Jutho ya señaló, implementar la transposición para tensores de orden superior no tiene sentido matemáticamente de todos modos).

No creo que eso sea exactamente lo que dije.

¿Qué funcionalidad se perdería si redefinimos * para que sirva como un operador de contracción similar a lo que @alanedelman tiene en mente para '*?

Perderíamos asociatividad: por ejemplo, (M_v) _v daría el punto actual (v, M_v) (un escalar), mientras que M_ (v_v) daría M._dot (v, v) (una matriz).

¿Por qué no hacemos del punto el operador de contracción (que de todos modos no es asociativo)? También podríamos definir contracciones de orden superior, por ejemplo, ddot (A :: Matrix, B :: Matrix) == A⋅⋅B == trace (A '* B).

Con esa línea de razonamiento, simplemente ya no necesitamos el operador de multiplicación para vectores y matrices, podemos escribir todo en términos de punto:
A ∙ B
A ∙ v
v ∙ A ∙ w
v ∙ w

Así que esa es solo la propuesta de @pwl pero con * reemplazado por un punto.

Pero, al menos personalmente, creo que la asociatividad es muy importante. Tienes que traducir entre matemáticas y la mayoría de los lenguajes de programación; para Julia, todavía espero que no tengas que traducir mucho.

Supongo que parte del problema es que incluso en matemáticas existen diferentes convenciones. ¿Está pensando en términos de matemáticas de vectores de fila y columna, o queremos el comportamiento más abstracto en términos de operadores lineales y espacios duales (donde supongo que un operador no se multiplica por un vector, sino que se aplica a un vector, entonces por qué no escribir ¿A (v) en lugar de A * vo A ∙ v)?

No tengo una gran necesidad de sintaxis de conveniencia, personalmente prefiero escribir punto (v, w) sobre v '* w cada vez, ya que el primero expresa más claramente la operación matemática independiente de la base (de hecho, primero se necesita definir una producto escalar antes de que se pueda definir un mapeo natural de vectores a vectores duales). =

Perdón por ser tan ignorante, pero ¿por qué exactamente M[1, :] puede ser una transposición, comportándose como v' ?

Voy a agregar a mi lista de cosas que me molestan

1.

[1 2 3] # ndims == 2
[1,2,3] # ndims == 1
[1;2;3] # ndims == 1

2.
v=rand(3)
v' * v actualmente tiene ndims == 1 (la propuesta de @StefanKarpinski corrige esto)
(v' * v)/( v' * v ) tiene ndims ==2 (esto realmente me molesta y también se arreglaría)

Todavía no quiero copartícipes
y la indexación de apl es algo en lo que sigo yendo y viniendo
--- sigo pensando, pero me encantaría ver la lista de cosas que
molestar a otras personas en julia actual

Definitivamente me gusta la idea de cdotcontratando el último índice al primer índice además del operador *
y permitir que el punto (a, b, ..) permita contracciones muy generales.

A pesar de la convención de nomenclatura de Numpy (y tal vez la apariencia de mi publicación anterior), tengo sentimientos algo encontrados sobre el uso de puntos para las contracciones tensoriales generales. Esta es la primera oración de Wikipedia:

En matemáticas, el producto escalar o producto escalar (o, a veces, el producto interno en el contexto del espacio euclidiano) es una operación algebraica que toma dos secuencias de números de igual longitud (generalmente vectores de coordenadas) y devuelve un solo número.

En mi mente, también, el punto debería devolver un escalar.

@ brk00 , el problema con su pregunta es que no está claro cómo extender esto a porciones de matrices de mayor dimensión / rango superior (realmente no me gusta la dimensión mundial para esto).

Perdón por ser tan ignorante, pero ¿por qué exactamente M [1,:] no puede ser una transposición, comportándose como v '?

Puede, pero ¿cómo generaliza esto?

@Jutho , lo siento, debería haberlo expresado de otra manera. Me refiero a su línea "La transposición de un elemento de algún espacio de producto tensorial de orden superior [...] no tiene una definición única", por lo que no hay una motivación matemática para introducir una definición en particular, o para definirla en absoluto.

@alanedelman :

Voy a agregar a mi lista de cosas que me molestan

[1 2 3] # ndims == 2
[1,2,3] # ndims == 1
[1; 2; 3] # ndims == 1

El comportamiento de concatenación no está relacionado con este problema. No enturbiemos más las aguas.

Definitivamente me gusta la idea de`cdot contratar el último índice al primer índice además del operador *
y permitir que el punto (a, b, ..) permita contracciones muy generales.

Esto también es tangencial. Centrémonos en las matrices y el corte.


En el enfoque actual, todas las matrices son de dimensión infinita con unos descartados implícitos.

Esto no es realmente cierto. Si esto fuera cierto, no habría distinción entre ones(n) , ones(n,1) , ones(n,1,1) , etc. Pero esos son todos objetos diferentes con diferentes tipos que ni siquiera son iguales a cada uno. otro. Hacemos un esfuerzo para implementar las cosas para que se comporten de manera similar, pero eso está muy lejos de que las matrices sean realmente de dimensión infinita.


La lista de cosas que son actualmente molestas refleja en gran medida (un subconjunto de) las buenas propiedades de mi propuesta anterior. A saber:

  1. Comportamiento de corte asimétrico: las dimensiones finales son especiales.
  2. v'' !== v - de hecho v'' != v ; esto se debe a que no tienen el mismo rango.
  3. v' != v - mismo trato.
  4. v'w es un vector de un elemento, no un escalar.
  5. necesitamos un análisis especial para A*_mul_B* , que es lo más terrible que jamás haya existido.

Estoy de acuerdo con la mayoría de los puntos de @StefanKarpinski , excepto por el bit v' == v : no creo que estos deban ser iguales. Sí, pueden ser isomórficos, pero siguen siendo objetos diferentes, ya que se comportan de manera muy diferente: las matrices M y M' también son isomorfos, pero no creo que nunca quisiéramos que fueran iguales (a menos que sean hermitianos, por supuesto).

Mi opinión general con los tipos Covector era que debían ser relativamente ligeros: aparte de las operaciones básicas (multiplicación, suma, etc.), evitaríamos definir demasiadas operaciones (incluso dudaría en definir la indexación ). Si quieres hacer algo con él, debes convertirlo de nuevo en un vector y hacer tus cosas allí.

+1 a la opinión de opinión de

También de acuerdo con @simonbyrne.

Sí, v y v' tienen tipos diferentes, pero ya consideramos que diferentes tipos de matrices con la misma forma y datos son iguales, por ejemplo speye(5) == eye(5) . ¿Por qué el covector es diferente al escaso?

Espero que los covectors se transmitan como matrices de filas. Para las matrices A y B que se consideran iguales, hasta ahora sostiene que

all(A .== B)

que no sería el caso si uno es un vector y el otro es un covector.

Para mí, un covector es más como una matriz de filas que un vector o una matriz de columnas.

Supongo que una pregunta clave es ¿qué es size(v' )? Si la respuesta es (length(v),) entonces creo que v == v' debería ser cierto. Si size(v') == (1,length(v)) entonces probablemente debería ser falso, pero posiblemente v' == reshape(v,1,length(v)) debería ser verdadero. Entonces, la pregunta es, ¿debería ser v' un tipo especial de vector o un tipo especial de matriz?

Cada vez estoy más convencido de que esta cuestión se trata realmente de lo que realmente significa transponer.

Los Covectors no son realmente matrices, por lo que no creo que podamos decir realmente qué "forma" tienen. Un covector se define realmente por lo que hace, es decir, *(::Covector, ::Vector) da un escalar: como cualquier AbstractVector no hace eso, no es realmente lo mismo.

Otro problema estaría en el campo complejo: ¿tendríamos v' == v o v.' == v ?

@simonbyrne :

Un covector se define realmente por lo que hace, es decir, *(::Covector, ::Vector) da un escalar: como cualquier AbstractVector no hace eso, no es realmente lo mismo.

Este es un buen punto. Sin embargo, puede resultar bastante frustrante si v' no se puede utilizar como objeto. Quizás el enfoque correcto es tratar v' como una matriz de fila divertida en lugar de un vector divertido.

Quizás el enfoque correcto es tratar v 'como una matriz de filas divertida en lugar de un vector divertido.

Algo así, pero tampoco creo que deba ser un subtipo de AbstractMatrix : creo que AbstractCovector debería ser un tipo de nivel superior. Me encantaría definir length(::Covector) , pero no creo que debamos definir un método size .

No estoy seguro de cómo manejar la radiodifusión: a menos que podamos llegar a criterios razonables, me equivocaría al no definir métodos de transmisión.

Esta discusión parece estar convergiendo hacia el uso de transposiciones y vectores como se usan en ingeniería, es decir, pensar en todo como si fueran matrices. Piense en los vectores como una columna y en los convectores como una fila. Esto es bueno para vectores y mapas lineales en el espacio cartesiano (el caso de uso típico), pero comienza a fallar si se intenta generalizar al álgebra multilineal oa espacios vectoriales más generales, etc. Parece haber muchas confusiones originadas por el hecho de que para espacios cartesianos muchas cosas son equivalentes que no son equivalentes para espacios generales. No me opongo necesariamente a esto como comportamiento predeterminado de Julia, pero realmente no estoy de acuerdo con algunas de las afirmaciones anteriores como si fueran “matemáticamente correctas”. Así que permítanme contradecir los de abajo.

El 20 de octubre de 2014, a las 17:39, Simon Byrne [email protected] escribió:

Estoy de acuerdo con la mayoría de los puntos de @StefanKarpinski , excepto por el bit v '== v: no creo que estos deban ser iguales. Sí, pueden ser isomórficos, pero siguen siendo objetos diferentes, en el sentido de que se comportan de manera muy diferente: las matrices M y M 'también son isomórficas, pero no creo que nunca quisiéramos que fueran iguales (a menos que sean hermitianas, por supuesto).

Esta afirmación no tiene sentido en ningún nivel.

1), creo que está abusando del significado de isomorfo aquí. Isomorfo es una relación entre dos espacios (en este escenario). En general, para cada espacio vectorial (real) V hay un espacio dual V * de funciones lineales (para espacios complejos, también existe el espacio conjugado y el espacio dual del espacio conjugado). Estos son espacios típicamente diferentes, y ni siquiera hay un mapeo único o natural de uno a otro, es decir, en general no tiene sentido asociar elementos v de V a elementos phi de V *. La única operación general es aplicar la función lineal, es decir, phi (v) es un escalar.

Se puede definir un mapeo natural de V a V * una vez que tenga una forma bilineal V x V -> escalares, típicamente un producto / métrica interna. Entonces se puede definir phi_i = g_ {i, j} v ^ j.

2), de hecho no existe una operación natural asociada a la "transposición de un vector" (ver punto 3). Esta confusión proviene de identificar vectores con matrices de columna.
Poniéndolo probablemente demasiado fuerte, en realidad me gustaría ver algún caso de uso de la transposición de un vector que no pueda obtener usando puntos y tal vez alguna operación de producto externo / producto tensorial.

Sin embargo, si desea utilizar la transposición como una forma de mapear vectores a vectores duales, es decir, para mapear v en V a algún phi = v 'en V_, entonces está asumiendo que está trabajando con el producto interno euclidiano estándar (g_ { i, j} = delta_ {i, j}). En ese punto, está erradicando la distinción entre índices covariantes y contravariantes, esencialmente está trabajando con tensores cartesianos, y V y V_ se vuelven naturalmente isomórficos. Como se indicó anteriormente, w_i = v ^ i = v_i = w ^ i, entonces sí, v == w e incluso diría que no hay nada que distinga a estos dos.

3) La operación “transponer” se define originalmente solo para mapas lineales de V -> W (http://en.wikipedia.org/wiki/Dual_space#Transpose_of_a_linear_map), y de hecho, incluso allí, puede que no signifique lo que piensa. La transposición de un mapa lineal A: V-> W es un mapa A ^ T de W _-> V_, es decir, actúa sobre vectores en W_, sobre vectores duales, y produce elementos de V_, es decir, covectores de V. Esto significa, si lo piensa en términos de la transposición habitual A ^ T de una matriz A, que esta matriz A ^ T se debe multiplicar con columnas que representan vectores en W * y que la columna que sale de esto representa un covector de V Entonces, en este punto, la identificación de vectores duales con vectores de fila ya falla.

Sin embargo, en el caso de uso típico de espacios vectoriales reales donde V * y W * se identifican con V y W a través del producto interno euclidiano estándar, la transposición de un mapa lineal se puede identificar con el adjunto de ese mapa lineal, que de hecho es un mapa de V-> W, como la mayoría de la gente piensa, también es el caso de la transposición del mapa. En el caso complejo, esto falla ya que la transposición de matriz ordinaria (sin conjugación compleja) solo tiene sentido como mapa W _-> V_, como mapa W-> V no es una definición independiente de la base y, por lo tanto, no tiene significado operativo.

En cuanto a lo que debería significar la transposición para matrices de dimensiones superiores, dentro del enfoque de matlab / ingeniería al que estamos convergiendo: debería ser un error, no se generaliza. Esto no significa que no haya forma de definirlo. El problema es qué representa una matriz de orden superior. ¿Es un elemento de un espacio de producto tensorial V1 \ a veces V2 \ veces ... \ veces VN, es un mapa multilineal que actúa sobre V1 x V2 x ... x VN, es un mapa lineal de algún espacio de producto tensorial V1 \ veces V2 \ veces … \ A veces VN a otro espacio de producto tensorial W1 \ a veces W2 \ veces ... \ veces WM? Para el caso bidimensional, hay una resolución. Identificando mapas lineales A: V-> W con vectores en W \ otimes V_, la transposición en el sentido del mapa lineal corresponde a invertir los espacios en este espacio de producto tensorial, A ^ i_j -> A_j ^ i con A ^ T en V_ \ otimes W = V * \ a veces W _, que de hecho es un mapa de W_ -> V. Lo bueno de invertir dimensiones para objetos de orden superior es que tiene una representación gráfica conveniente.

En conclusión, creo que el problema es si el vector de Julia necesita capturar las propiedades de un vector general arbitrario en el sentido matemático, o si solo representa una columna de números, una lista, ... Las palabras vector, tensor, ... tienen bien - significados operativos e independientes de base definidos en matemáticas, que pueden entrar en conflicto con el tipo de operaciones que desea definir en un vector de Julia. A la inversa, algunos objetos son realmente vectores desde el punto de vista matemático (vectores duales, etc.) que probablemente no deberían identificarse con los vectores de Julia, ya que el tipo de vector estándar de Julia (abstracto) no tiene forma de distinguir entre diferentes espacios vectoriales.

En ese sentido, existe cierta asimetría, ya que el término Matriz está mucho menos sobrecargado, incluso desde el punto de vista matemático, una matriz es solo una representación conveniente de un mapa lineal en cierta base. Tenga en cuenta que también hay muchos casos en los que no desea representar un mapa lineal como una matriz, sino más bien como una función (este problema ha surgido antes, por ejemplo, en los argumentos de eigs, etc.). En ese sentido, puedo ver por qué matlab ni siquiera se molestó en tener un vector, una estructura verdaderamente unidimensional. En este enfoque, todo se interpreta simplemente como una "matriz", es decir, un bloque con números, de todos modos.

¿Pregunta? Sea c un covector. ¿Qué es fft (c)?

Respuesta: fft (c ')' que toma conjugados complejos de formas potencialmente inesperadas
en comparación con fft (c)

los usuarios podrían beneficiarse de que no esté definido
(¿O quizás esto sería bueno para los usuarios, si está bien documentado?)

Apuesto a que hay muchas más de este tipo de cosas

Sospecho que lo correcto en Base por ahora es solo definir:

  • Covector veces otro vector
  • covector multiplicado por una matriz
  • covector 'para obtener un vector

+1 al soporte mínimo para covectors por ahora.

Sí, +1.

Entonces, si menciono eso, estoy bastante seguro de que
norma (covector, q) debe ser norma (vector, p) donde 1 / p + 1 / q = 1
de la desigualdad de Holder, que no se implementaría por mucho tiempo. :-)

Gracias a Dios por p = q = 2

Eso no sería tan difícil de implementar:

norm(c::Covector, q::Integer) = norm(c.vector, q/(1-q))

Probablemente desee una comprobación adicional para evitar q == 0 y q == 1 .

Creo q == 1 estaría bien

Med venlig hilsen

Andreas Noack

2014-10-22 15:19 GMT-04: 00 Stefan Karpinski [email protected] :

Eso no sería tan difícil de implementar:

norma (c :: Covector, q :: Entero) = norma (c.vector, q / (1-q))

Probablemente desee algunas comprobaciones adicionales para evitar q == 0 yq == 1.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -60139762.

Estoy trabajando en una redacción de la propuesta de covector (que apoyo principalmente), pero puede ser útil publicar ahora un punto que precisa la noción de @StefanKarpinski de "matriz de filas divertida".

Los Covectors no son vectores, pero la descripción de por qué no siempre es clara. Un covector (o bra-vector, como lo llamarían los físicos) es un funcional lineal que se come un vector y escupe un número que es un producto escalar.

Más precisamente:

  • Deje que V = V(F) y W = W(F) sean vectores sobre algún campo de elementos F ,
  • v y w sean vectores que son elementos de V y W respectivamente,
  • <.,.> ser un producto interno tal que <.,.> : V × W → F y v, w ↦ <v, w>

El covector correspondiente a v es el funcional lineal v' : W → F que realiza el mapeo w ↦ <v, w> .

La descripción habitual termina diciendo que v' es un elemento de un espacio dual V* sin mucho más sobre lo que el espacio dual _es_.

Para la gente de ciencias de la computación, hay una descripción simple de v' como una combinación del producto interno con respecto a su primer parámetro, y V* como la colección de funciones que son un parámetro curry con diferentes primeros vectores.

Este artículo de Wikipedia puede ser útil para reconciliar las diferentes notaciones asociadas con las dos descripciones, pero expresan exactamente el mismo concepto.

Además, inicialmente pensé que la indexación en un covector no debería estar permitida. Sin embargo, indexar v'[1] es equivalente a aplicar v' al vector de base canónica e₁ = (1, 0, ...) , de modo que v'[1] se puede definir como <v, e₁> . La semántica de indexación más general como 1:n sigue naturalmente de la aplicación de v' a una matriz de proyección adecuada, siendo cada columna un vector de base canónica.

Si se considera la semántica de indexación de los covectors en el contexto de # 987, se da otra razón por la cual los covectors no son AbstractVector s: en general, no es posible indexar v' forma barata porque todo depende de cuán caras sean como <v, e₁> son para calcular. En general, podría tener un producto interno definido con respecto a una matriz <v, w> = v'*A*w y el costo de indexación está dominado por el producto matvec A*w (o A'*v ), que ciertamente es demasiado caro para calificar como AbstractVector .

Ya que estamos hablando de DFT y normas ... esto se me pasó por la cabeza hoy

si f es una función de un vector y produce un escalar, entonces me gustaría que diff(f) sea ​​una función que acepte un Vector y produzca un Covector de modo que f(x) ~= f(x0) + diff(f)(x0) * (x-x0) . Un uso muy común del gradiente es obtener una aproximación lineal del cambio incremental en la salida de la función dado un cambio incremental en la entrada. Si la entrada es un vector, entonces parece natural que el gradiente sea algo que se contraiga en todas las dimensiones de la entrada.

Pero si tuviera que implementar el descenso de gradiente, necesitaría agregar (una versión escalada de) el gradiente en x a sí mismo. En este sentido, el gradiente es un vector que apunta en la dirección más empinada, cuya norma es proporcional a la tasa de cambio a lo largo de esa dirección más empinada.

Mi instinto dice que "el gradiente de la función de entrada de vector en un punto es un covector" es más importante.

Sospecho que lo correcto en Base por ahora es solo definir:

Covector veces otro vector
covector multiplicado por una matriz
covector 'para obtener un vector

A pesar de la impresión que pueda haber tenido de mis horribles publicaciones anteriores, haga +1 en esto.

Sin embargo, algunas observaciones sobre esto:

Estoy trabajando en una redacción de la propuesta de covector (que apoyo principalmente), pero puede ser útil publicar ahora un punto que precisa la noción de @StefanKarpinski de "matriz de filas divertida".

Los Covectors no son vectores, pero la descripción de por qué no siempre es clara. Un covector (o bra-vector, como lo llamarían los físicos) es un funcional lineal que se come un vector y escupe un número que es un producto escalar.

Creo que los vectores / covectores duales (también prefiero el término funcionales lineales) se pueden definir sin tener un producto interno. Es solo que necesita un producto interno si desea definir un mapeo de V a V *

Más precisamente:

Sea V = V (F) y W = W (F) un vector sobre algún campo de elementos F,
v y w son vectores que son elementos de V y W respectivamente,
<.,.> ser un producto interno tal que <.,.>: V × W → F y v, w ↦
Es bastante extraño, y creo que imposible, tener un producto interno definido entre dos espacios vectoriales diferentes V y W. ¿Cómo se define la propiedad de la definición positiva?
El covector correspondiente av es el funcional lineal v ': W → F que realiza el mapeo w ↦.

La descripción habitual termina diciendo que v 'es un elemento de un espacio dual V * y no se dice mucho más sobre lo que es el espacio dual.

Creo que está bastante claro, ciertamente para el caso de dimensión finita, que el espacio dual, por su mismo nombre, es un espacio vectorial. Sin embargo, el resumen de mi perorata anterior es que, el tipo de vector (abstracto) de Julia se parece más a una lista. Puede usarse para representar ciertos vectores y otras estructuras de datos unidimensionales que no tienen la estructura matemática de un vector, pero no es lo suficientemente general para capturar todos los objetos que son vectores matemáticos (ya que no puede distinguir entre diferentes vectores espacios).

Teniendo en cuenta la semántica de indexación de los covectores en el contexto de # 987, se da otra razón por la cual los covectors no son AbstractVectors: en general, no es posible indexar v 'de manera barata porque todo depende de cuán caras sean= v'_A_w y el costo de indexación está dominado por el producto matvec A_w (o A'_v), que ciertamente es demasiado caro para calificar como AbstractVector.

Esta es una especie de argumento de bucle. Como se mencionó anteriormente, un funcional lineal (covector) es más general y existe incluso para espacios vectoriales sin producto interno. En segundo lugar, cuando la métrica es una matriz A definida positiva, el mapeo natural de un vector v a un covector es de hecho v'_A. Sin embargo, ¿qué es esta v 'aquí? ¿Es ya un mapeo de v a un covector diferente definido con respecto a la norma euclidiana estándar (con una identidad como métrica)? No, no es. Si los vectores tienen índices contravariantes (superiores) y los covectores tienen índices covariantes (inferiores), entonces la métrica A tiene dos índices inferiores y el producto interno es= v ^ i A_ {i, j} v ^ j. Y entonces este mapeo se puede escribir como (con phi el covector asociado al vector v) como phi_j = v ^ i A_ {i, j}. (Tenga en cuenta que esto es diferente de un operador lineal típico, que tiene la forma w ^ i = O ^ i_j v ^ j). Por tanto, la v en esta expresión v'_A sigue siendo la que tiene un índice superior, sigue siendo el mismo vector.

Entonces, en realidad, uno de los puntos que estaba tratando de señalar anteriormente es que las personas a menudo escriben v 'cuando en realidad no están tratando de trabajar con un covector. Solo intentan escribir expresiones que se definen en vectores, como un producto internoo algo como v ^ i A_ {i, j} w ^ j dentro de la representación de matriz familiar como v'_A_w. Incluso el producto interno euclidiano estándar v'w debe leerse como v ^ i delta_ {i, j} w ^ j y no requiere la introducción de convectores. Como se mencionó anteriormente, creo que el uso de covectors adecuados reales que no son una forma oculta de productos escalares es bastante limitado en la mayoría de las aplicaciones. La gente simplemente escribe v 'para poder usar una sintaxis matricial conveniente, pero lo que realmente están haciendo es calcular productos internos, etc., que se definen con respecto a los vectores, no a los codificadores.

Aparte, hubo un comentario antes sobre la asociatividad de la multiplicación si tuviéramos v '== v como en la propuesta original de Stefan. Sin embargo, incluso sin esto, no diría que * es asociativo, incluso si v 'es un covector que no se identifica con los vectores ordinarios:
A_ (v'_w) es una matriz
(A_v ') _ w está produciendo un error

El gradiente de una función con valores escalares es de hecho una de las aplicaciones adecuadas de los covectores, es decir, sin argumento, el gradiente es un covector. Lo que se asume implícitamente en aplicaciones como el gradiente conjugado es que existe una métrica (y en realidad una métrica inversa) para mapear estos covectores en vectores. De lo contrario, no tiene sentido hacer algo como x ^ i (vector de posición) + alpha g_i (gradiente). La forma correcta de escribir esto es x ^ i + alpha delta ^ {i, j} g_j donde delta ^ {i, j} es la métrica inversa. Si se intenta definir técnicas de optimización en una variedad con una métrica no trivial, entonces es necesario tener en cuenta esta métrica (inversa). Hay un buen libro sobre esto:
http://sites.uclouvain.be/absil/amsbook/
y un paquete de matlab correspondiente:
http://www.manopt.org

El 22 de octubre de 2014, a las 22:52, goretkin [email protected] escribió:

Ya que estamos hablando de DFT y normas ... esto se me pasó por la cabeza hoy

si fis es una función de un vector y produce un escalar, entonces me gustaría que diff (f) sea una función que acepte un vector y produzca un Covector de modo que f (x) ~ = f (x0) + diff (f) ( x0) * (x-x0). Un uso muy común del gradiente es obtener una aproximación lineal del cambio incremental en la salida de la función dado un cambio incremental en la entrada. Si la entrada es un vector, entonces parece natural que el gradiente sea un covector.

Pero si tuviera que implementar el descenso de gradiente, necesitaría agregar (una versión escalada de) el gradiente en x a sí mismo. En este sentido, el gradiente es un vector que apunta en la dirección más empinada, cuya norma es proporcional a la tasa de cambio a lo largo de esa dirección más empinada.

Mi instinto dice que el gradiente es un covector que es más importante.

-
Responda a este correo electrónico directamente o véalo en GitHub.

A_ (v'_w) es una matriz
(A_v ') _ w está produciendo un error

Oh mierda.

b2e4d59001f67400bbcc46e15be2bbc001f07bfe05c7c60a2f473b8dae6dd78a

Este hilo estaba atrasado para una publicación con una imagen humorística.

@Jutho Admito que no estoy usando la forma más general de espacio dual, pero no veo cómo mi descripción es en absoluto circular dada mi elección de definir un espacio dual usando el marco de un espacio de Banach. El formalismo espacial de Banach ya es excesivo para los espacios vectoriales de dimensión finita de todos modos.

Circular no es el término correcto. Lo que estaba tratando de decir con ese párrafo es que puedo revertir esta línea de razonamiento con respecto a la evaluación eficiente de entradas, ya que, por ejemplo, en el caso del gradiente (conjugado) en variedades curvas, supongo que el gradiente (covector) g_i se puede calcular de manera eficiente, pero el vector correspondiente A ^ {i, j} g_ {j} que se agregará ax ^ i (con A ^ {i, j} la métrica inversa) podría no ser eficiente. Acabo de notar lo mal delineado que apareció mi mensaje anterior en Github; estaba bien cuando escribí el correo electrónico. Traté de arreglarlo ahora. No quiero seguir repitiéndome, ni quiero dar la impresión (equivocada) de que no estoy de acuerdo con las propuestas actuales. Los únicos puntos que estaba tratando de hacer.

  1. El espacio dual es ciertamente un espacio vectorial, por lo que sus elementos son vectores. Sin embargo, el objetivo no debería ser que todos los objetos que tienen el significado matemático de un vector sean un subtipo de AbstractVector de Julia, ni todos los objetos que son subtipos de AbstractVector tengan las características matemáticas adecuadas. de un vector. En ese sentido, me gusta mucho más el nombre Matrix que el nombre Vector , ya que tiene mucha menos connotación. Por lo tanto, supongo que puedo estar de acuerdo con el hecho de que, sea cual sea el tipo Covector se introduzca como parte de esta propuesta, no debería ser un subtipo de AbstractVector o incluso AbstractArray , incluso en aquellos casos en los que V * es naturalmente isomorfo a V (espacio cartesiano, que constituye probablemente la mitad de las aplicaciones).
  2. El producto interno le permite construir un mapeo de V a V_, pero no es un requisito previo para la existencia de V_. Ciertamente, esto suena como un tema irrelevante ya que en programación siempre tienes un espacio vectorial de dimensión finita y siempre hay un producto interno (aunque puede que no sea el relevante para la aplicación), pero el punto práctico que estaba tratando de hacer es esto. Hay aplicaciones adecuadas de covectors en programación, como el buen ejemplo de gradiente de @goretkin , pero en la mayoría de las aplicaciones donde las personas escriben v' , no están realmente tratando de construir un covector, solo están tratando de escribir un bilineal mapeo entre dos vectores (es decir, V x V a escalar), como en v'_A_w = v ^ i A_ {i, j} w ^ j o incluso v'w = v ^ i delta_ {i, j} w ^ j usando la conveniente representación matricial. Prefiero escribir dot(v,A*w) explícitamente, pero esa es una elección personal. Dado que no tenemos información sobre los índices superiores o inferiores en las matrices, se pueden construir casos de uso en los que en una expresión escalar como v'*A*w ambos v' y w pueden ser vectores o covectores en el sentido matemático. La única consecuencia de esto es que no estoy seguro de si estoy realmente contento con llamar al tipo de v' Covector , ya que al igual que el nombre Vector tiene demasiadas matemáticas connotación que podría no estar justificada en la mayoría de las aplicaciones, lo que luego conduce a más discusiones (en su mayoría irrelevantes) como esta.

@jutho +1 por gradiente siendo covectors
Primero aprendí esto de la tesis doctoral de Steve Smith y he usado esta idea
para explicar claramente esta vaga idea de gradiente conjugado para cálculos de valores propios
que la gente solía hablar en química y física.

Me he quedado cada vez más impresionado por la coherencia interna
y autónomo es el mundo de tensores de rango 2 con un índice superior y otro inferior.
Esto es lo que convencionalmente llamamos cálculos matriciales o simplemente álgebra lineal antigua.
Diablos, aunque puedo encontrar muchos ejemplos como norma (v, p) o fft (v) donde funciones
difieren si son vectores o covectores, realmente no tengo un solo buen ejemplo (¡todavía!)
de una función que es naturalmente diferente en un vector y una matriz de una columna.
(Que alguien me ayude, seguramente debe haber uno, ¡¡incluso muchos !!)

También desde hace varios años me ha preocupado como @jutho ese vector abstracto
los espacios aún no habían encontrado su camino hacia julia, recuerdo haber hablado con @StefanKarpinski
sobre esto en mi pizarra hace años. Aún así, las preocupaciones generales de
1) los recién llegados a Julia deben tener una experiencia fácil y
2) desempeño
debe triunfar sobre estas cosas elegantes.

Como resultado de hablar con @jiahao , realmente me
Álgebra lineal (matrices con una contra y una co) y hay tensores simples
(todo el mundo es un co, no contras). Este último funciona bien en APL y Mathematica.
El primero es todo álgebra lineal y se capturó mejor en MATLAB antes
injertaron en matrices de dimensión superior a 2. Existe la generalidad más completa
que después de una palabra con @JeffBezanson parecía que no era tan loco.

por cierto, creo que ha tomado alrededor de un año, si no más, pero finalmente nos lo estamos tomando en serio :-)

Respecto a
A_ (v'_w) es una matriz
(A_v ') _ w está produciendo un error

Sólo la multiplicación matricial "*" es asociativa. La matriz de tiempos escalares sobrecargada nunca fue
de asociación. Ni siquiera en matemáticas, ni siquiera en MATLAB.

A_ (v'_w) es una matriz
(A_v ') _ w está produciendo un error

Buena atrapada. Sin embargo, ¿existen casos en los que la ausencia de errores produzca respuestas diferentes? Una pregunta relacionada: ¿qué debería hacer un covector escalar *?

La matriz de tiempos escalares sobrecargada nunca fue asociativa. Ni siquiera en matemáticas, ni siquiera en MATLAB.

Las operaciones que involucran solo matrices y escalares son asociativas (ver punto 3). Como podemos tratar los vectores como matrices de 1 columna, incluirlos tampoco causa ningún problema. El problema aquí es que el Covector es un operador que puede reducir dimensiones (mapas de vectores a escalares), así que no estoy seguro de cómo encaja eso.

La adición de matrices escalares es una preocupación mayor, ya que arruina la distributividad (aunque si mal no recuerdo, creo que el debate se resolvió para mantenerla):

(A+s)*v != A*v + s*v

Este es un resumen muy agradable:

Como resultado de hablar con @jiahao , realmente me
Álgebra lineal (matrices con una contra y una co) y hay tensores simples
(todo el mundo es un co, no contras).

Está claro que esta discusión no se trata de respaldar el caso más general, eso es demasiado confuso para las personas que no necesitan la generalidad completa, no se pueden incluir en la jerarquía de AbstractArray actual y es más adecuado para un paquete (que en realidad estoy trabajando en). Pero la discusión es, de hecho, cuál de estos dos mundos especiales queremos apoyar, ya que no son compatibles entre sí.

En el primero, todos los vectores v son columnas, todos v' son covectores y todas las matrices son operadores lineales V-> W (por ejemplo, viven en W ⊗ V_) Esto excluye la representación de, por ejemplo, bilineal asigna V x V -> escalar (por ejemplo, v ^ i A_ {i, j} w ^ j) ya que esto requiere una matriz con dos índices más bajos (por ejemplo, viviendo en V_ ⊗ W_). Por supuesto, aún puede usarlo en la práctica y escribirlo como v'_A*w , pero entra en conflicto con la nomenclatura elegida de los objetos. Además, no especifica en qué espacio viven las matrices de orden superior.

El último caso resuelve ese problema ya que tiene (V == V *), pero conduce a resultados sorprendentes como v' == v . Además, esta solución está realmente limitada a espacios vectoriales reales, ya que incluso en espacios complejos con un producto interno euclidiano estándar (es decir, 'espacio cartesiano complejo'), el espacio dual no es naturalmente isomorfo a V sino a conj (V) (V bar), el espacio vectorial conjugado (ver espacios de Hilbert en http://en.wikipedia.org/wiki/Complex_conjugate_vector_space)

Con respecto a la no asociatividad, en ese sentido el comportamiento actual, donde v'*v no produce un escalar sino una matriz, es en realidad más consistente, ya que entonces tanto A*(v'*v) como (A*v')*v produce un error.

El lado del "Álgebra lineal" de las cosas se puede resolver adicionalmente en diferentes opciones de cómo representar vectores y covectores:

  • La propuesta de Covector, también conocida como "vector de fila divertido", que hemos discutido recientemente.
  • La semántica de "no vectores verdaderos" que fusiona (N-vectores como Nx1-matrices), (N-vectores-fila como 1xN-matrices), que está respaldada por Matlab y similares.
  • La forma actual de Julia: no fusionar N-vectores densos y matrices Nx1, combinar N-vectores dispersos y matrices Nx1, representar N-vectores-fila como matrices 1xN.

Me pregunto si es estrictamente necesario combinar también (escalares, 1-vectores y matrices 1x1) en el mundo de los "no vectores verdaderos"; @alanedelman y yo estuvimos discutiendo esto ayer y en álgebra lineal numérica la conmutatividad de vector * escalar y vector escalar * se usa en todas partes, sin embargo, el producto vector * escalar es el que no le importa si está haciendo (N,) * ( ,) o (N, 1) * (1,1).

1) Al final del día, lo más importante son A) la facilidad de uso y B) el rendimiento.
2) Los dos mundos deben poder coexistir en completa armonía. Al hacer trabajos de ingeniería, mi opinión es que no hay nada más intuitivo y fácil que la notación tensorial. Una sugerencia, si se me permite, podría ser habilitar un indicador de entorno o se podría importar un paquete al comienzo de un programa. Esto permitiría un uso sencillo y una lógica de programación sencilla. ¿No es esto posible o debemos elegir un esquema general?

Parece que hay dos opciones.
1) habilitar la importación de una caja de herramientas, un parche o un indicador ambiental para que funcione al mismo tiempo
2) construir un lenguaje que sea capaz de unificar los mundos especiales, pero que sea fácil de usar y rápido

No estoy seguro de cuán prometedor sea esto, pero me encontré con un área de investigación potencialmente interesante.

¿Puedo dirigir su atención a:

Grupo de investigación de álgebra geométrica
http://www.mrao.cam.ac.uk/~clifford/pages/introduction.htm

Curso de conferencias en álgebra geométrica
http://www.mrao.cam.ac.uk/~clifford/ptIIIcourse/course99/

Aplicaciones físicas del álgebra geométrica
http://www.mrao.cam.ac.uk/~clifford/ptIIIcourse/

Un lenguaje matemático unificado para la física y la ingeniería en el siglo XXI
http://www.mrao.cam.ac.uk/%7Eclifford/publications/ps/dll_millen.pdf

Álgebra geométrica
http://arxiv.org/pdf/1205.5935v1.pdf

Fundamentos de la computación álgebra geométrica
http://link.springer.com/book/10.1007%2F978-3-642-31794-1

Computación de álgebra geométrica
http://link.springer.com/book/10.1007%2F978-1-84996-108-0

Después de analizar esto un poco más, parece realmente sorprendente.

Mientras el álgebra y la geometría se han separado, su progreso ha sido lento y sus usos limitados; pero cuando estas dos ciencias se han unido, se han prestado fuerzas mutuas y han marchado juntas hacia la perfección.

  • Joseph Louis Lagrange

Imagínese necesitar anteojos y no saber que necesita anteojos. Luego, cuando consigues anteojos, el mundo cambia inesperadamente. GA es como gafas para el interior de tu cerebro.

  • Pablo Colapinto

Este tipo parece tener la idea correcta ... https://github.com/wolftype/versor

Las bibliotecas de operaciones matriciales típicas tienen plantillas de funciones en línea para la multiplicación de vectores y matrices. El álgebra geométrica combina muchas otras matemáticas (matrices, tensores, vectores y álgebras de mentiras). Versor es similar, pero con esteroides, donde los vectores y las matrices dispersas de varios tamaños se denominan simplemente multivectores y representan elementos geométricos más allá de las direcciones xyz y las matrices de transformación. Círculos, líneas, esferas, planos, puntos son todos elementos algebraicos, al igual que los operadores que hacen girar, retuercen, dilatan y doblan esas variables. Tanto estos elementos como operadores son multivectores que se multiplican de muchas, muchas formas diferentes.

Este video detalla muy bien Versor, el lenguaje matemático GA programado.
https://www.youtube.com/watch?v=W4p-e-g37tg

Estas son las diapositivas para esto. https://github.com/boostcon/cppnow_presentations_2014/blob/master/files/generic_spaces.pdf

Puede hacer algunos cálculos de tensor asombrosos, realmente fácilmente, optimizados para la velocidad cuando compila.

@ esd100 , creo que sería útil mantener la discusión sobre este hilo centrada en el tema específico del título.

@johnmyleswhite Hice una búsqueda del término "tensor" y fue mencionado 171 veces (ahora 172 veces). Si bien estoy de acuerdo con su afirmación, en general, este hilo tiene 157 comentarios (ahora 158), algunos de los cuales tratan directa e indirectamente con el título de la publicación original (Tomando en serio las transposiciones vectoriales). Creo que mi publicación trata indirectamente con el título de la publicación original al ofrecer una mayor perspectiva de lo que se puede hacer con las nuevas aplicaciones de las matemáticas tensoriales, a través del álgebra geométrica. Mi opinión es que incorporar el tipo de poder de dimensión superior que Versor tiene en Julia sería una característica beneficiosa adicional de Julia. El título del video de YouTube era "Programación genérica de espacios genéricos: Álgebra geométrica en tiempo de compilación con C ++ 11". No veo por qué no podría ser Julia en lugar de C ++. Por otra parte, no soy un matemático ni un informático.

@ esd100 Las álgebras geométricas son interesantes, pero no son lo más general que cubre este número. El álgebra geométrica que nos interesaría principalmente es el álgebra de Clifford real Cl (R ^ n, I); sin embargo, también nos interesarían otras álgebras de Clifford que no son álgebras geométricas, como las álgebras de Clifford como vectores complejos Cl (C ^ n, I) o incluso álgebras sobre campos arbitrarios o anillos no conmutativos Cl (F ^ n, I) . Además, ni siquiera queremos restringirnos necesariamente a las álgebras de Clifford, sino que queremos considerar cómo el álgebra lineal se generaliza a la configuración más general del álgebra tensorial donde las formas cuadráticas (productos internos) no están necesariamente definidas.

En la configuración del álgebra tensorial, la propuesta " v' es una operación no operativa " en el OP tiene mucho sentido, ya que se generaliza consistentemente al extensiones bialgebraicas que conducen a carbongebras tensoriales donde la "v" produce una propuesta de covector es muy natural. Sin embargo, no estoy seguro de cómo la propuesta de "ningún vector verdadero" se generaliza en un álgebra tensorial.

@jiahao Gracias por su respuesta tan informativa. Es bueno que alguien con cierto dominio del tema arroje algo de luz. Me interesaría investigar más esos temas para comprenderlos mejor. Tengo curiosidad por saber por qué existe el código súper optimizado para BLAS, pero el código súper optimizado para álgebras más complejas, como Clifford Algebra no.

No estoy seguro de si esta discusión aún está en curso, pero quería compartir mi opinión porque estos problemas son algunas de las partes más importantes (y molestas) de mí al usar Julia. Estoy de acuerdo con algunas de las anteriores y con otras no.

Básicamente, creo que debemos darnos cuenta de esto: todos los usuarios de Julia querrán matrices multidimensionales rápidas (para el almacenamiento de datos) como ya se implementó en Julia. Muchos / la mayoría de los usuarios también estarán interesados ​​en el álgebra lineal, y los arreglos antes mencionados son una forma conveniente de empaquetar los datos contenidos en vectores y matrices. Algunos usuarios querrán hacer álgebra de tensor genérico (multilineal).

La pregunta es ¿cómo extender estos arreglos "desnudos" a los conceptos matemáticos del álgebra lineal? ¿Cómo logras que la sintaxis parezca matemática normal? ¿Se sentirá cómoda la gente con que v ''! = V? Etcétera etcétera.

Lo primero es que no queremos empeorar las matrices, solo para poder hacer álgebra lineal. No queremos agregar datos adicionales a Vector o argumentos de plantilla innecesarios a Array. Queremos ser tan rápidos como C / C ++ cuando no estamos haciendo álgebra lineal (y con suerte tan rápido como C / fortran, cuando lo hagamos).

La segunda cosa que todos debemos darnos cuenta es que la contracción tensorial multidimensional completa requiere cantidades significativas de información adicional. Para tensores de hasta 2 dimensiones, escriba cualquier multiplicación como A_B'_C * D ... mientras que para los tensores generales es más natural pensar en un gráfico para definir la contracción (un diagrama de red tensorial o un gráfico de factores; lo mismo ocurre por muchos nombres diferentes). Para tensores de alta dimensión, tiene sentido envolver las matrices desnudas y decorarlas con espacios vectoriales, etc. para realizar un seguimiento de todo. Esto se puede hacer en paquetes como aquellos en los que Jutho (y posiblemente otros) está trabajando. No tiene sentido considerar el significado de 'en tensores tridimensionales o más; nadie estaría interesado en usar esa función cuando existen permutedims, o si están usando un paquete de tensor dedicado.

Los usuarios principales deberán multiplicar matrices, sumar vectores, etc. Querrán transponer matrices. También querrán un producto interno y un producto externo. Nos guste o no, estos se definen en conjunto con un espacio dual. Sabemos cuáles son estos valores para números reales y números complejos, y los tenemos predefinidos. Y sí, es cierto que el espacio dual de un espacio vectorial finito real se siente básicamente como lo mismo, y creo que esto es lo que nubla todo el asunto. El mayor problema actual es que no tenemos un vector dual adecuado. Sin él, no podemos "escribir" ecuaciones en Julia como lo hacemos en lápiz y papel, ¡un gran problema! Aún más importante, no tenemos v '' = v. Esto es muy poco intuitivo.

La gente solo querrá o necesitará crear un vector dual para realizar productos internos o externos. Una simple envoltura decorativa para decirle al compilador qué hacer cuando vuelva a encontrarse con * es una solución sensata y no debería tener ninguna sobrecarga de tiempo de ejecución. Creo que estos vectores / covectores duales deberían ser indexables, sumables / sustractables, multiplicados por un escalar, multiplicados por un vector (devolviendo un escalar) y multiplicados por una matriz (devolviendo un covector), ¡y eso es todo! Ni siquiera realizaría explícitamente la conjugación compleja para vectores complejos, que podrían integrarse en el producto escalar, el producto externo, la indexación, etc. para mejoras de velocidad / memoria.

No me preocupa que agreguemos complicaciones al sistema de tipos. Creo que esto es necesario para encapsular las matemáticas tal como las aprendieron los usuarios en la escuela secundaria y la universidad: tener asociativo * (donde esté bien definido), tener v '' = v, tener tanto "sujetadores" como "kets" (I estoy usando la notación de Dirac de álgebra lineal aquí), etc.

Por cierto, si esto se ha implementado en Julia 0.4, ¡no lo sé! Todavía estoy trabajando para entender 0.3.4 ...

@andyferris , en general estoy de acuerdo con todo esto. Creo que mi propuesta aquí podría modificarse ligeramente y funcionar bien: en lugar de que M*v' sea ​​un error, haga que produzca un covector y en lugar de que v*M sea ​​un error, haga que produzca un vector. Para mí, esto equivale conceptualmente a que M sea ​​agnóstico sobre si sus dimensiones están hacia arriba o hacia abajo; puede escribir v'*M*w o v*M*w' para un producto interno y cualquiera de los dos funcionará.

Un pensamiento que ha estado rebotando es tener matrices row-major y col-major y tener M.' simplemente cambiar de una a otra y, de manera similar, que v' sea ​​la variación de fila principal en v - que tiene la misma forma pero se almacena conceptualmente en el otro orden. Aunque no estoy seguro de esto.

+1 a @andyferris . También creo que solo queremos tipos de envoltura simples Transpose y ConjTranspose que nos permitan usar la sintaxis concisa del álgebra matricial para escribir productos de vectores, matrices y transposiciones de los mismos. Con respecto al punto 2 de la propuesta de @StefanKarpinski , no restringiría estos contenedores para que sean un contenedor de objetos AbstractArray y no haría que los tipos formen parte de la jerarquía de tipos AbstractArray . Transpose(x) debería ser el tipo que resulta de escribir x' en una expresión y permite diferir su evaluación / tener una evaluación diferida, dependiendo del resto de la expresión, enviando Transpose para el resto de las operaciones en la expresión (que probablemente será el operador de multiplicación * en el 99,9% de los casos). Sin embargo, las personas también deberían poder darle a esta sintaxis un significado para nuevos tipos que podrían no ser parte de la jerarquía AbstractArray , como los objetos de factorización matricial u otros tipos para definir, por ejemplo, operadores lineales.

Para el caso específico de matrices y vectores, realmente no veo el caso de uso de M*v' , pero no me opongo intrínsecamente a él. Lo importante es que cumpla con las expectativas básicas (es decir, las reglas 2, 4, 5, 6 y 8 del final de la propuesta de Stefan).

El principal punto final de la discusión es probablemente la interacción con el corte. ¿Debería un segmento de fila de una matriz ser automáticamente Transpose ? Mi voto aquí es sobre las reglas de división de APL donde este no es el caso.

@Jutho , la motivación es hacer que * asociativo - A*(v'w) y (A*v')*w funcionen y produzcan los mismos resultados, módulo de no asociatividad del tipo de elemento subyacente (por ejemplo, flotante- punto).

Para que (A*v')*w dé el mismo resultado que A*(v'*w) , A*v' debería ser una matriz N=3 . ¿Eso es lo que tenías en mente? Extrañaba completamente esto.

OK, muy interesante, tengo algunos comentarios.

Primero debemos dirigirnos a los usuarios principales. Básicamente, el uso de Array{T,n} como n -dimensional es su función principal. Se tiene que tener sentido para los programadores que no tienen idea acerca de álgebra lineal, sino que quieren manipular datos de Julia. Debido a esto, bajo ninguna circunstancia un segmento de matriz puede devolver un co-vector. Este es un concepto de álgebra estrictamente lineal que se aplica solo a ciertos tipos de datos T que pueden definir un espacio vectorial y su dual (por ejemplo, datos numéricos o "campos").

Podría ir de cualquier manera con el corte APL. Parece bastante intuitivo. Es de tipo estable y no hace nada sorprendente. Aunque no creo que sea necesario hacer este cambio para que el álgebra lineal sea autoconsistente ... podría ser bueno (aunque puede ser un cambio importante). Me parece inquietante que M[1,:] sea ​​de tamaño 1xn y M[:,1] sea ​​de tamaño n (no nx1) ... parece demasiado asimétrico ... pero supongo que es un buen recordatorio de cómo están las cosas presentado en la memoria. De todos modos ... este cambio solo debe realizarse si tiene sentido para el almacenamiento y la manipulación de datos abstractos. Para mí, este cambio es ortogonal a la discusión del álgebra lineal.

Por lo tanto, incluso si no implementamos las reglas de corte APL, aún podemos rescatar álgebra lineal significativa de manera bastante sencilla. Si desea el vector de columna i th de M llame a colvec(M,i) y si desea el vector i th fila co de M llame rowvec(M,i) . Si i fuera una tupla o un vector, podría devolver una tupla o un vector o (co) vectores (¿podría esto ser útil para razonar sobre la paralelización en algunos casos?). Si prefiere una matriz, simplemente use la notación de indexación normal. Si usamos reglas de división APL, lo mismo es muy útil para distinguir la acción de las divisiones de fila y columna con el símbolo * . (Hay que considerar cómo se comporta la conjugación compleja con rowvec ).

De esa manera, si está haciendo álgebra lineal, todo tendrá sentido y el usuario no tendrá que preocuparse por la regla de corte en particular que implementa Julia. El operador ' solo actuaría sobre vectores y covectores (para cambiar la decoración) y sobre matrices (ya sea de forma directa o perezosa). Puede ser más fácil recordar cómo extraer vectores base de una descomposición propia, que siempre parece olvidar.

Para * , creo que el álgebra matricial / vectorial en Julia debería funcionar para que se vea y se sienta como las matemáticas como el operador de multiplicación y nunca como el operador del producto tensorial entre dos vectores, dos covectores, dos matrices, etc. no debería permitir M*v' , porque estrictamente en matemáticas necesitas escribir la ecuación con el símbolo "\ otimes" (el signo de las horas dentro de un círculo), no el símbolo estándar de multiplicar. ¡Usar el símbolo de multiplicar su significado está mal definido! No podemos tener w*M*v' == v'*M*w porque la multiplicación de matrices no es conmutativa. De nuevo, por M*v'*v no tiene sentido hablar de asociatividad de * . Puede interpretar esto como innerproduct(outerproduct(M,v'),v) que requiere dos símbolos de multiplicación diferentes, o como una multiplicación escalar M * innerproduct(v',v) donde _siste_ tiene sentido usar * para ambos. Con esta expresión podríamos requerir poner entre corchetes, o quizás el compilador podría buscar un orden significativo de operaciones (observe aquí que el orden de evaluación más rápido es también el que considero el único orden válido de evaluación).

Con vectores, covectores y matrices podemos tener un sistema algebraico que sea consistente con las matemáticas estándar. Siempre que desee realizar el producto externo entre dos vectores, dos covectores o dos matrices, se está moviendo inherentemente al álgebra multilineal o al álgebra tensorial genérica. Aquí posiblemente podría tener matrices y vectores que se multipliquen como kron(M,N) usando un nuevo símbolo. O puede solicitar a los usuarios que utilicen un paquete completo de álgebra multilineal. O lo que sea ... parece más allá del alcance de la pregunta original (que fue hace 14 meses, por cierto ...)

Entonces, para resumir, veo cuatro cosas casi completamente distintas que suceden aquí:

  1. Mejore la división de Array s de datos arbitrarios mediante las reglas de división de APL.
  2. Haga que el álgebra lineal en Julia sea más intuitivo y totalmente consistente con las matemáticas agregando algunos conceptos simples: covectores y un método para extraer vectores y covectores de matrices, y operaciones entre covectors, matrices, etc. el uso de matrices 1xn y nx1 continuará funcionando como se esperaba, y el vector se comportará igual.
  3. Para mayor velocidad, implemente la evaluación perezosa para transpose , conj , etc. mediante el uso de tipos de envoltura muy parecidos a covector, pero también para matrices.
  4. Desarrolle un paquete tensorial de propósito general y posiblemente agréguelo a la biblioteca estándar.

¿Posiblemente solo el primero será un cambio radical? Los otros mejoran o se suman a la funcionalidad actual. Todos se pueden implementar de forma más o menos independiente, y probablemente todas sean cosas valiosas para hacer. (PD: si quieres cortar APL, recomiendo hacerlo pronto , antes de que Julia se vuelva demasiado "grande" y rompa demasiados paquetes, etc.)

Sí, lo siento, mi sugerencia de permitir M*v' realmente no tiene ningún sentido ya que las diferentes formas de asociar productos producen formas completamente diferentes. Entonces, creo que si vamos a cambiar esto, mi propuesta original es el camino a seguir. Hasta ahora, esa parece ser la mejor combinación con el comportamiento de corte de APL. Por supuesto, si queremos un comportamiento de corte de APL o no es una consideración de nivel superior.

Solo quería decir algunas cosas y, por favor, tómelas con un grano de sal. Son solo opiniones y sé que las mías no tienen mucho peso. Sin embargo, una cosa me llamó la atención mientras leía el comentario de @andyferris . No creo que esté de acuerdo con el comentario de que tiene que tener sentido para los programadores que no tienen idea sobre el álgebra lineal pero que quieren manipular datos en Julia. No creo que los programadores sean la audiencia para la que deberían escribirse los programas matemáticos. Si un programador quiere manipular datos, hay muchos lenguajes que le permitirán hacerlo. Julia es un lenguaje para la informática y debería permitir una informática técnica sofisticada.

@ esd100 Es cierto, pensé en eso. Pero con Julia, pensé que queríamos ser codiciosos ... queríamos satisfacer muchos propósitos y sobresalir en muchas cosas. Puede haber razones científicas genuinas para manipular datos no numéricos. Puede haber científicos actuales o ex-científicos que sean usuarios de Julia que quieran hacer una programación más general. Mi punto anterior era que no deberíamos hacer Array más lento o tomar más memoria dándole un campo adicional para hablar sobre transposición / conjugación.

Si uno realiza el corte APL, entonces los cortes de fila o columna deben devolver el mismo objeto. La pregunta era: sin el corte de APL, ¿qué es lo mejor para M[1,:] para devolver? Bueno, si M[1,:,:,:,:] devuelve un Array{T,n} , creo que confundiría a todos si M[1,:] devolviera un covector{T} . ¿Qué pasa si dejamos M = zeros(3,3,3) y llamamos M[1,:] , que actualmente devuelve una matriz de 1x9? ¿Deberíamos hacer de esto un covector también? ¿Qué pasa si M fuera Array{String,3} ?

Para mí, esto me parecería bastante sorprendente y confuso. Tampoco es consistente con los cambios de corte de APL, si eso sucediera en el futuro. Por lo tanto, estaba sugiriendo agregar nuevas funciones para propósitos de álgebra lineal, rowvec(M,i) y colvec(M,i) . No es ideal, agrega nuevas funciones ... pero al menos está claro lo que deberían devolver para propósitos de álgebra lineal. Era lo único en lo que podía pensar que tenía buenas propiedades para matrices genéricas y buenas propiedades para álgebra lineal (junto con un tipo covector), sin intentar atender al álgebra multilineal. Si alguien tiene una notación más agradable para extraer covectores de matrices, ¡eso también sería bueno!

@ esd100 : Estoy bastante seguro de que los programadores son la audiencia para la que está diseñada Julia. La computación científica es la fortaleza de Julia, pero hay muchos usuarios de Julia que la usan solo como un lenguaje de programación de propósito general.

Estoy de acuerdo con @andyferris en que hay que dividir los requisitos del tensor de los requisitos del contenedor.

Sin haber leído todo el hilo, mi problema personal es que si tengo una matriz 3D A (por ejemplo, datos tomográficos) y no

imagesc( data[1,:,:] )

esto no funciona. En mi humilde opinión, data[1,:,:] , data[:,1,:] y data[:,:,1] deben ser matrices 2D (o submatrices). Actualmente utilizo una función squeeze autodefinida para solucionar este problema.

Una vez más, esta es solo mi opinión y muy poco importante, dado que estoy lejos de la acción. Siento que cuando se desarrolla una aplicación, debería tener un conjunto de principios de diseño que la guíen. Los constructores deben enviar un mensaje claro y unificado sobre su propósito y su identidad. Si el objetivo es desarrollar una plataforma informática técnica rápida, intuitiva y sofisticada, y ese es el punto fuerte de Julia, apéguese a ella. No envíe señales contradictorias al tratar de adaptar su propósito a la audiencia de programadores generales.

El punto es que incluso para aplicaciones puramente científicas o matemáticas, hay varias interpretaciones contradictorias, por ejemplo, hay varios objetos matemáticos que podría representar usando vectores y matrices y, por lo tanto, no debería tomar decisiones demasiado específicas.

Es mejor tener paquetes dedicados para satisfacer las necesidades de disciplinas específicas siguiendo un conjunto específico de convenciones.

@jutho Mi instinto es que tiene razón, pero me pregunto cuál es la base de su conclusión de que los paquetes son "mejores" y en comparación con qué alternativas.

Esto es muy sencillo. La funcionalidad que es importante para la mayoría de los usuarios de Julia pertenece a Base. La funcionalidad que es más especial pertenece a un paquete.

Por supuesto, no es muy fácil trazar una línea aquí. Pero la mejor manera de poner algo en la base es crear un paquete para que mucha gente pueda probarlo. Varias funciones básicas que han aterrizado en Base se desarrollaron por primera vez en un paquete.

@ esd100 , no entiendo completamente tu pregunta. Al final, lo que debería proporcionar un lenguaje científico son las estructuras de datos y los métodos para representar y calcular con los objetos típicos utilizados en la ciencia. Pero ciertas estructuras de datos pueden ser útiles para representar diferentes estructuras matemáticas y, a veces, una representación diferente puede ser conveniente para objetos del mismo tipo. Por lo tanto, asociar una estructura matemática fija a un tipo de datos puede ser demasiado restrictivo para algunas disciplinas y demasiado complejo para otras disciplinas. Por lo tanto, esto no debe perseguirse en la base de Julia, sino mediante paquetes específicos que solo intentan satisfacer las necesidades de una disciplina.

Con respecto a la discusión actual, todos los que trabajen con vectores y matrices esperarán la posibilidad de transponer un vector y tener v ^ T * w = escalar. Pero estos vectores y matrices pueden usarse para representar una serie de cosas diferentes, y esto probablemente dependerá del campo / disciplina. Di ejemplos de dónde v ^ T podría no ser un covector real en las publicaciones anteriores.

El 11 de enero de 2015, a las 18:10, esd100 [email protected] escribió:

@jutho https://github.com/jutho Mi intuición es que tiene razón, pero me pregunto cuál es la base de su conclusión de que los paquetes son "mejores" y en comparación con qué alternativas.

-
Responda a este correo electrónico directamente o véalo en GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -69501771.

Esto es muy sencillo. La funcionalidad que es importante para la mayoría de los usuarios de Julia pertenece a Base. La funcionalidad que es más especial pertenece a un paquete.

No creo que sea tan simple. Por ejemplo, existen funciones de Bessel en Base, y es muy poco probable que sean importantes para la mayoría de los usuarios de Julia. La razón por la que pueden estar en Base es que hay un estándar razonablemente universal para las funciones de Bessel, y es probable que nadie use esos nombres para otra cosa, o espere que hagan algo diferente.

A través de convenciones de nomenclatura cuidadosas (algunos podrían decir detalladas), Mathematica puede colocar más de 4000 funciones en el lenguaje central, y me parece extremadamente conveniente casi nunca tener que cargar un paquete. Por el contrario, cuando estoy usando Python / Sage, no es inusual que un archivo / cuaderno importe explícitamente 200 funciones en la parte superior (evitando la sintaxis "de ___ importación *" más imposible de rastrear). Cada vez que necesito usar una función que no está incorporada, tengo que seguir una serie de pasos adicionales:

(1) Intente recordar si ya lo he usado en ese archivo y / o realice una búsqueda de texto para confirmar (restringiendo a palabras completas si el nombre es una subcadena de otra cosa)
(2) O: (a) agregue el significado de inmediato, perdiendo el hilo de mis pensamientos y encontrándolo nuevamente; o (b) intentar recordar agregar la importación después de haber hecho lo que estaba haciendo, manteniendo otra cosa en la memoria
(3) Para funciones menos comunes, posiblemente tenga que buscar en qué paquete está

Esto puede resultar molesto y debilitante. Así que creo que, como las funciones de Bessel, cualquier cosa que se considere estándar --- y por lo tanto no causará conflictos de nombres o confusión --- debería estar en Base, sin importar si mucha gente lo usa. Ciertamente álgebra lineal.


Volviendo al tema, hemos estado hablando de varias capas de funcionalidad --- pasando de arreglos como contenedores semánticamente desnudos (como se implementa en Base.AbstractArray ), a objetos de tensor cartesiano con semántica arriba / abajo (como lo describe mi AbstractTensorArray propuesta , a objetos tensoriales más generales con índices mapeados a espacios vectoriales (como en TensorToolbox de @Jutho ) --- de generalidad creciente y potencial de optimización decreciente.

Creo que es importante que los usuarios puedan realizar una transición fácil entre estas capas, entre otras cosas porque los usuarios _es posible que no sepan_ qué nivel de generalidad necesitan realmente cuando comienzan. Como ejemplo simple, @Jutho señaló que en el ejemplo de procesamiento de imágenes de @jdbates , los usuarios podrían querer asociar diferentes subconjuntos de índices a distintas geometrías, para procesar las coordenadas de la imagen de una manera y el espacio de color de otra; pero si hubieran comenzado usando solo uno de estos geométricamente, debería ser conveniente ascender a una representación más general que permita usar cada uno con su propia geometría. Si las funciones adicionales (o patrones de llamada de función) que se activan en cada nivel de generalidad usan valores predeterminados razonables --- por ejemplo, índices ascendentes / descendentes e índices neutrales en AbstractTensorArray s a espacios vectoriales cartesianos predeterminados únicos y espacios de "secuencia", respectivamente --- entonces se vuelve casi transparente. Los usuarios de una capa inferior de funcionalidad no necesitan conocer ni preocuparse por las capas superiores hasta que las necesiten.

Cualquier parte de esto para la que sea claro y predecible --- para los usuarios relevantes --- cuál debería ser la jerarquía, podría ir razonablemente en Base. La pregunta real debería ser --- ya sea para operaciones de matriz, álgebra lineal o álgebra tensorial --- ¿qué tan probable es que un usuario de este tipo de funcionalidad lo espere o quiera de una manera diferente?

A mucha gente no le gusta la ridícula granularidad de la cantidad de importaciones que necesita para hacer algo en Python-land, no creo que nadie esté proponiendo llegar a esos extremos.

Pero hay un argumento importante de que una gran cantidad de dependencias y la saturación de la biblioteca no son deseables para algunos casos de uso, Julia, el lenguaje realmente no debería requerir tener las bibliotecas de tiempo de ejecución de Fortran instaladas en su computadora, pero en este momento la biblioteca estándar de Julia lo hace, ya sea que el código que desea ejecutar está haciendo cualquier álgebra lineal (o funciones de Bessel, o FFT, etc.). Sin embargo, todo esto está bien cubierto por # 5155 y otros temas: "instalado por defecto" y "mínimo necesario para cualquier funcionalidad" deberían eventualmente separarse en diferentes conjuntos de módulos.

Gracias Tony, lo apoyo. Además, con nuestro sistema de paquetes no es realmente un problema tener paquetes tipo "caja de herramientas" que agrupen varios. Con la macro @reexport esto funciona bien.

Pero hay otro punto. Dentro de un paquete, es mucho más fácil hacer avanzar una idea. Si uno quiere cambiar algo, simplemente lo hace. Dentro de la Base uno es mucho más restringido.

Parece que si bien tener un mosaico de paquetes permite una mayor independencia, también crea una plataforma fragmentada que es más difícil de navegar. Parece que el uso de una estructura generalizada más unificada agilizaría y facilitaría la interacción entre disciplinas y la exploración de nuevos dominios.

¿Cuál es actualmente la forma más idiomática de hacer vector * matrix?
Por ejemplo, digamos que tengo X con forma (K, N) y b con forma (K,) , y quiero multiplicar por b en el a la izquierda para obtener un vector de longitud (N,) .
¿Llamo BLAS.gemv('T',1.0,X,b)
O reshape(b'*X,size(x,2))
Ambos son un poco feos.

Supongo que podrías hacer X'b

2015-03-13 17:15 GMT-04: 00 joschu [email protected] :

¿Cuál es actualmente la forma más idiomática de hacer vector * matrix?
Por ejemplo, digamos que tengo X con forma (K, N) yb con forma (K,), y quiero
multiplicar por b a la izquierda para obtener un vector de longitud (N,).
¿Llamo a BLAS.gemv ('T', 1.0, X, b)
O remodelar (b '* X, tamaño (x, 2))
Ambos son un poco feos.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -79405868.

Sí, pensé que eso activaría una copia de X.
Pero @time parece indicar que no hay copia, según la asignación de memoria

julia> X = rand(1000,1000); y = rand(1000);

julia> <strong i="8">@time</strong> y'X;
elapsed time: 0.00177384 seconds (15 kB allocated)

julia> <strong i="9">@time</strong> X'y;
elapsed time: 0.000528808 seconds (7 kB allocated)

Hacemos un análisis elegante de 'por lo que X'y termina como Ac_mul_B(X,y) y hace que
misma llamada BLAS que sugirió.

2015-03-13 17:28 GMT-04: 00 joschu [email protected] :

Sí, aunque pensé que eso activaría una copia de X.
(Pero @time https://github.com/time parece indicar que no hay
copiar, basado en la asignación de memoria

julia> X = rand (1000,1000); y = rand (1000);

julia> @time y'X;
tiempo transcurrido: 0,00177384 segundos (15 kB asignados)

julia> @time X'y;
tiempo transcurrido: 0.000528808 segundos (7 kB asignados)

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -79421713.

Lo crea o no, una redacción de todo este hilo está cada vez más cerca de materializarse.

Un último punto: ¿alguien ha considerado eso? en realidad podría ser un animal totalmente diferente de '? Este último tiene la estructura adecuada por ser un dual, pero '. no lo hace si el campo subyacente no son los números reales. ¿Es una locura asignar? la noción de "invertir todos los índices" de transponer y reservar todas las nociones de dualidad a ', que produce un "vector de fila divertido" / conjugado hermitiano?

Creo que en cualquier caso conj(transpose(x)) debería ser equivalente a ctranspose(x) .

Como se argumentó anteriormente, espero que ninguno de los tipos de envoltura que transpose y ctranspose crearán reciba un nombre que contenga un concepto matemático específico como dual . Un lenguaje científico como el de Julia debería proporcionar un conjunto de estructuras de datos útiles y debería implementar operaciones comunes, pero creo que sería un error asociarle una estructura matemática estricta, ya que esto no será apropiado para algunas aplicaciones y demasiado complejo para otros. Depende del usuario utilizar estas estructuras de datos y operaciones para implementar las operaciones matemáticas en su aplicación. Ciertamente hay aplicaciones para z.' * A * z devuelven un escalar donde z es un vector complejo y A alguna matriz, aunque esto no tiene nada que ver con un producto interno y / o dual vectores.

@jutho, ¿ podrías dar un caso de uso para z.' * A * z ?

Si z1 y z2 son la representación de dos vectores complejos Z1 y Z2 (por ejemplo, vectores tangentes en la parte holomórfica del espacio tangente de una variedad de Kähler ) y a es la representación matricial de un tensor A con dos índices covariantes (por ejemplo, una forma compleja (2,0) de la variedad de Kähler) y luego A(Z1,Z2) = z.' * a * z .

Tenga en cuenta que enfatizo aquí que los objetos julia z1 , z2 y a solo forman una _representación _ de ciertos objetos matemáticos (con respecto a una base / coordinización elegida) y por lo tanto las operaciones sobre estructuras de datos no pueden asociarse de forma única a operaciones matemáticas sin saber qué representan estas estructuras de datos.

@jutho gracias. Su punto sobre las representaciones está bien tomado y ha sido representado muchas veces en esta discusión. Estoy tratando de encontrar la interfaz mínima de vectores y matrices, y ver si esa interfaz mínima es de alguna manera fundamentalmente incompatible con la interfaz mínima de matrices, y qué podemos descargar en tipos de datos abstractos definidos por el usuario.

En este punto, estoy totalmente a favor de la propuesta de @StefanKarpinski , también en su mayoría repetida por @andyferris arriba. En particular

  1. Indexación de estilo APL
  2. v 'da algún tipo de Covector o Transpose

Todo lo demás es un detalle. Sería bueno agregar las funciones row(M,i) y col(M,i) . De lo contrario, para extraer una fila como un covector, supongo que necesitaría M[i,:].' ? IIUC, M[i,:]' haría un conj no deseado en este caso?

Sí, debo agregar que no aprecié completamente la bondad de la indexación estilo APL la última vez que escribí, pero ahora estoy totalmente a favor de hacer ese cambio. Esto, a su vez, hace que las cosas del vector dual sean aún más atractivas y las funciones row / col .

Desde entonces intenté jugar con una implementación y lo más confuso fue si el covector extraído de una matriz estaba conjugado o no ...

Creo que quieres tomar los valores literales de la matriz. Usando la notación de Dirac, expanda (cualquier) matriz M = sum_i | i>es, por ejemplo, [0,0,1, ..., 0], queremos extraerU obtenemos row(U,i)' = col(U’,i) que tiene mucho sentido para extraer los vectores propios izquierdo y derecho de una descomposición propia.

Andy

El 15 de marzo de 2015, a las 9:36 pm, Jeff Bezanson [email protected] escribió:

En este punto, estoy totalmente a favor de la propuesta de @StefanKarpinski https://github.com/StefanKarpinski , también en su mayoría repetida por @andyferris https://github.com/andyferris arriba. En particular

Indexación de estilo APL
v 'da algún tipo de Covector o Transpose
Todo lo demás es un detalle. Sería bueno agregar las funciones de fila (M, i) y col (M, i). De lo contrario, para extraer una fila como un covector, supongo que necesitarías M [i ,:] '. ? IIUC, M [i ,:] '¿haría un conj no deseado en este caso?

-
Responda a este correo electrónico directamente o véalo en GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -81228816.

¿Algún voluntario para empezar a trabajar en esto? por ejemplo @mbauman, @jakebolewski quién estoy sorprendido de ver no son de este hilo ya :)

Encontrar todo lo que necesita cambiar puede ser tedioso, pero el cambio básico en el comportamiento de indexación no debería ser tan malo. Probablemente @jiahao y @andreasnoack puedan decir más sobre cómo deberían incorporarse los Covectors, por ejemplo, cuál debería ser su supertipo en todo caso.

Necesitamos 9, no 8 comentarios más antes de poder seguir adelante con esto.

Yo puedo ayudar con eso.

estamos bastante cerca

Como comentario relevante, si habrá un tipo de contenedor Transpose y CTranspose , también debería ser un tipo de contenedor Conjugate simple, de modo que conj(A) sea también vago. Para multiplicar matrices con BLAS, esto no es realmente útil ya que no hay soporte especial para eso (y C en BLAS significa conjugado hermitiano), pero si alguna vez hay una implementación completa de Julia BLAS, sería genial también admitir conj(A)*B sin conjugación explícita.

Yo, por mi parte, siento que ahora me tomo las transposiciones vectoriales mucho más en serio que antes.

Quizás @andreasnoack y @simonbyrne puedan decirnos si es necesario volver a visitar el número 6837.

Estoy de acuerdo con @simonbyrne en que no deberíamos tener Transpose{Array} <: AbstractArray .

Otros pensamientos generales:

  • Me di cuenta de que el cálculo del producto externo actualmente contiene una excepción a la regla "no agregar automáticamente dimensiones de singleton finales". Si u tiene el tamaño (n,) , u * u' es un cálculo que involucra (n,) x (1, n) . Este producto no se puede calcular usando las reglas ordinarias de la multiplicación de matrices _ a menos que automáticamente remodelemos el primer argumento para que sea (n, 1) .
  • La regla de "agregar automáticamente dimensiones de singleton finales" de la semántica de indexación de MATLAB es fundamentalmente incompatible con la regla "transponer invierte todas las dimensiones". Con la regla anterior, las matrices de forma (n,) son semánticamente idénticas a las matrices remodeladas de forma (n,1) y forma (n,1,1) , etc. Pero tenga en cuenta que si la transposición invierte todas las dimensiones, entonces el las matrices resultantes tienen forma (n,) , (1, n) y (1,1,n) , que _no_ pueden ser equivalentes si solo se le permite agregar singleton finales. Llevando esto al extremo lógico, la transposición de una matriz puede tener un número arbitrario de singleton _leading_ y por lo tanto tiene una forma ambigua, que es lógicamente inconsistente.

También hice una encuesta de literatura y descubrí una historia interesante de APL. A lo que nos hemos referido como la regla de indexación APL no estaba en el libro de Iverson de 1962, pero sí existía en APL \ 360 (1968; la primera implementación de APL). Sin embargo, APL \ 360 combinó escalares y 1-vectores, una inconsistencia que no fue reconocida en la literatura hasta (Haegi, 1976). Una discusión sobre la semántica formal de las matrices multidimensionales apareció por primera vez en la tesis doctoral de Brown (1972; luego diseñó APL2), y estimuló una línea de trabajo que formaliza su semántica.

Un excelente estudio de los desarrollos que llevaron a APL2 es:

  • Karl Fritz Ruehr. "Una encuesta de extensiones de APL". En Proceedings of the International Conference on APL, APL '82, páginas 277–314, Nueva York, NY, EE. UU., 1982. ACM.

En la literatura se destacan por la atención prestada a las reglas de indexación:

  • T. Más. "Axiomas y teoremas para una teoría de matrices". IBM Journal of Research and Development, 17 (marzo): 135-175, 1973.

    • Un tomo gigante que formaliza la semántica de matrices multidimensionales utilizando la teoría de conjuntos axiomáticos de Quine, que muestra cómo los escalares se pueden combinar con matrices de rango 0 con la noción de conjuntos autocontenidos para construir lo que la literatura APL llama "matrices flotantes". ([1] == 1, en contraste con "arreglos con conexión a tierra" donde [1]! = 1. El trabajo posterior de Sheila M. Singleton en su tesis de maestría de 1980 mostró que la teoría de arreglos de More también podría adaptarse para describir arreglos con conexión a tierra.

    • More también menciona el producto interno que devuelve un escalar como una regla importante para guiar la semántica de la matriz.

    • More también alude a la complejidad de manejar "up / down-ness" para matrices multidimensionales generales:

      "Sea V un espacio vectorial n-dimensional sobre un campo. Sin tener en cuenta las consideraciones de contravarianza y covarianza, un tensor de valencia q en V es un mapeo multilineal del producto cartesiano de la lista VV ... V de longitud q en un vector espacio. Si V tiene una base, entonces un tensor de valencia q en V puede ser representado por un _tensor de componente_, que es una matriz en q ejes, cada uno de longitud n ".

  • G. Lewis. "Un nuevo sistema de indexación de matrices para APL", Actas de la séptima conferencia internacional sobre APL - APL '75, 234-239, 1975.

    • Este artículo fue el primero en defender que la tupla de índice en una operación de indexación sea un objeto de primera clase en APL, y señaló que se puede lograr una construcción consistente del resultado de la indexación mediante productos cartesianos sistemáticos a lo largo de cada rango de la tupla de índice.

  • Hans R Haegi. "La extensión de APL a estructuras de datos en forma de árboles". ACM SIGAPL APL Quote Quad, 7 (2): 8–18, 1976.

    • Este artículo se quejaba de una inconsistencia en APL \ 360 clásico, que combinaba escalares y matrices 1, y defendía que la regla de indexación APL requería que esta combinación no se mantuviera.

    • Este artículo también contiene una construcción muy similar a Lewis, 1975; el trabajo parece ser independiente.

  • JA Gerth y DL Orth. "Indexación y fusión en APL". En Proceedings of the International Conference on APL, APL '88, páginas 156–161, Nueva York, NY, EE. UU., 1988. ACM.

    • Observó que la regla de indexación APL puede justificarse pensando en la indexación como una operación de difusión que correlaciona el conjunto de índices con el conjunto de valores. Esta interpretación funcional sugiere naturalmente la regla de preservación de rango y la construcción cartesiana de Lewis y Haegi.

Además, sin la regla "se pueden agregar dimensiones de singleton finales", no obedecemos la identidad

image

ya que el lado izquierdo es un escalar (por definición de trazo) y el lado derecho tiene forma (1, n) x (n, n) x (n,) = (1,) . Por el contrario, podemos tomar esta identidad como un principio rector para seleccionar la semántica de transposición de vectores. El punto principal es la propiedad cíclica de la operación de rastreo que define la primera identidad. La cantidad dentro de la traza debe ser un escalar o una matriz (la traza de un vector no está definida). Avv' ya es inequívocamente una matriz: (Av)v' y A(vv') producen el mismo resultado. Pero también de la segunda cantidad, v'Av debe ser una matriz _o_ un escalar. (Si es escalar, la segunda identidad también es válida). v'Av puede ser un 1-vector solo si la regla "puede agregar dimensiones de singleton finales" está activa, en cuyo caso se puede remodelar de forma transparente a una matriz 1x1.

Por lo tanto, si queremos que se defina la traza, entonces necesariamente impone restricciones sobre las formas permitidas de los productos externos vv' y de las formas cuadráticas v'Av .

@jihao : No estoy muy seguro de lo que está argumentando a favor nuestro. ¿Puede indicar "se puede agregar la regla de dimensiones de singleton finales" con un poco más de claridad? ¿Cuándo aplica? ¿Crees que deberíamos apoyarlo?

Creo que algunos de sus argumentos se pueden tomar para fortalecer la posición de que no podemos pensar en la transposición como una inversión de todas las dimensiones (un vector de columna se transpondría a un vector de columna, o posiblemente una matriz con un número arbitrario de dimensiones iniciales de singleton). Para mantener la coherencia con el álgebra matricial, creo que debe tomarse como un intercambio de las dos primeras dimensiones. Entonces creo que desaparecen algunas de las contradicciones que afirma. Con suerte, el resto desaparecerá si no agregamos ninguna dimensión singleton al transponer (la primera dimensión ausente del covector no cuenta).

Mis comentarios generales anteriores no son para defender estas reglas, sino más bien para ayudar a delinear el espacio de diseño de las reglas de indexación de matrices y su interacción con las identidades del álgebra lineal.

MATLAB utiliza la regla "puede agregar dimensiones finales de singleton" y (según @alanedelman) se introdujo para admitir matrices multidimensionales. El cálculo de índice a desplazamiento utilizado en matrices MATLAB se define mediante sub2ind , que devuelve el mismo índice lineal independientemente de cuántos 1 finales le arroje. Además, la documentación de indexación matricial de MATLAB establece que para las operaciones de indexación:

El número de subíndices especificado para B, sin incluir los subíndices finales iguales a 1, no supera los ndims (B).

Más formalmente, creo que se puede afirmar como:

Para operaciones que toman matrices como entrada, (n,) -arrays, (n,1) -arrays, (n,1,1...) matrices están todas en la misma clase de equivalencia con respecto a ser argumentos válidos para estas operaciones. (Si n=1 , entonces la clase de equivalencia también incluye escalares).

Ejemplos:

  • A*b y A\b donde A es una matriz y b puede ser un vector o una matriz (semántica idéntica para matrices n x 1 ),
  • hcat(A, b) donde A es una matriz y b puede ser un vector o una matriz

En su mayor parte, Julia no tiene esta regla de "se puede agregar la regla de dimensiones de singleton finales", excepto en el ejemplo del producto externo. Posiblemente también haya otros, pero no puedo pensar en ellos ahora mismo.

Siempre que Transpose{A<:AbstractArray} no sea un subtipo de AbstractArray , creo que no veo ningún problema (pero probablemente estoy pasando por alto algo porque no lo he pensado tanto como ustedes) :
con

typealias AbstractVectorTranspose{A<:AbstractVector} Transpose{A}
typealias AbstractMatrixTranspose{A<:AbstractMatrix} Transpose{A}
typealias AbstractTMatrix Union(AbstractMatrix, AbstractMatrixTranspose} 

(y de manera similar por CTranspose ) podemos tener

AbstractVectorTranspose * AbstractVector = Number
AbstractVector * AbstractVectorTranspose = AbstractMatrix
AbstractVectorTranspose * AbstractTMatrix = AbstractVectorTranspose
AbstractTMatrix * AbstractVector = AbstractVector
AbstractTMatrix * AbstractTMatrix = AbstractTMatrix

La única pregunta abierta es si AbstractVector * AbstractTMatrix debería admitirse cuando AbstractTMatrix tiene 1 como primer tamaño, o si AbstractVector * AbstractVectorTranspose es suficiente.

Además, el nuevo sistema de tipos podría ayudar a expresar algunos de esos typealias y union un poco más de cuidado.

Además, si, por ejemplo, v.'*A se calcula como (A.'*v).' , entonces aparece la necesidad de un contenedor Conjugate si A sí mismo es, por ejemplo, A=B' .

Estoy de acuerdo con @simonbyrne en que no deberíamos tener Transpose{Array} <: AbstractArray .

¿Puedes explicarlo allí? Pensé que la opinión en https://github.com/JuliaLang/julia/issues/4774#issuecomment -59428215 era que CoVector no debería ser un subtipo de AbstractVector, pero me parecería un poco extraño no tener Transpose{Matrix} <: AbstractArray .

Por lo que vale, creo que CoVector debería comportarse principalmente como Vector excepto que Vector convierte en Matrix como una matriz de columna mientras que CoVector convierte en Matrix como una matriz de filas.

Supongo que eso significaría que la indexación en un covector debería funcionar igual que en una matriz de filas.

Ese es un pensamiento interesante. ¿Las cosas se vuelven más fáciles o más complicadas si solo se eliminan las dimensiones singleton _leading_ en los tipos de transposición / covector?

(He estado siguiendo este tema con interés, pero mi álgebra lineal está lo suficientemente oxidada como para no sentirme calificado para contribuir).

@mbauman :

¿Las cosas se vuelven más fáciles o más complicadas si solo se eliminan las dimensiones iniciales singleton en los tipos transpose / covector?

Si permite un número arbitrario de dimensiones iniciales singleton, entonces los índices de matriz ya no están bien ordenados y no existe "la" primera dimensión, lo cual es lo suficientemente extraño como para que podamos tomar el orden correcto como un axioma.

@tkelman :

Pensé que la opinión en # 4774 (comentario) era que CoVector no debería ser un subtipo de AbstractVector, pero me parecería un poco extraño no tener Transpose {Matrix} <: AbstractArray.

Me quedó claro que este problema se trata completamente de separar la semántica de matrices (cf # 10064) de la semántica de álgebra lineal, y buscar lugares donde se entremezclan.

  • La semántica de matriz se define por funciones como tamaño, longitud, getindex, setindex, hcat, vcat, reshape, rot90 ...
  • La semántica del álgebra lineal se define mediante funciones como +, -, *, /,, ', trace ...

Si definimos las funciones cat como parte de la interfaz esencial de un AbstractArray , entonces Transpose{<:AbstractArray} claramente no es un AbstractArray porque sus comportamientos de concatenación son diferentes . Si consideramos que solo la forma y la indexación son parte de la interfaz esencial, la situación es menos clara.

Si requerimos la concatenación como parte de la interfaz esencial de un AbstractArray , entonces también es más fácil justificar por qué tipos como SymTridiagonal no son AbstractArray s, ya que las operaciones de concatenación sobre SymTridiagonal s como [SymTridiagonal(randn(5), randn(4)) randn(5)] actualmente no están definidos.

@toivoh :

Supongo que eso significaría que la indexación en un covector debería funcionar igual que en una matriz de filas.

Hay identidades que sugieren que los comportamientos de indexación de Transpose{Vector} deberían ser los mismos que los de Vector s ordinarios. Considere que para los tipos numéricos, v[1] produce el mismo resultado que v' * e₁ = v ⋅ e₁ y v[1:2] produce el mismo resultado que v' * [e₁ e₂] , donde e₁ es la base canónica Vector{Int} [1, 0, 0, ...] y e₂ es [0, 1, 0, 0, ...] . Si requerimos que se mantengan estas identidades relacionadas con la indexación, los productos internos y la transposición, entonces se podría afirmar que

(v')[1] == (e₁' * v'') == (v' * e₁)' == (v ⋅ e₁)' == conj(v ⋅ e₁)* = conj(v[1])

(donde el primer paso es un axioma nuevo y el cuarto hace uso del hecho de que la transposición de un escalar no es una operación) de modo que indexar en Transpose{Vector} esencialmente ignoraría la transposición, e indexar en CTranspose{Vector} conjugaría los elementos indexados.

Me dice que este problema trata completamente de separar la semántica de matrices (cf # 10064) de la semántica de álgebra lineal, y buscar lugares donde se entremezclan.

  • La semántica de matriz se define por funciones como tamaño, longitud, getindex, setindex, hcat, vcat, reshape, rot90 ...
  • La semántica del álgebra lineal se define mediante funciones como +, -, *, /,, ', trace ...

+1 a ese punto de vista y no tener Transpose <: AbstractArray . Además, al indexar un covector, debe ser con un solo índice, ya que de lo contrario el resultado del vector covector * (contraerse sobre un solo índice) no podría resultar en un escalar (un objeto con índices cero).

@jihao : No estoy seguro de

(v')[1] == (e₁' * v'')

como un nuevo axioma. Aunque incluso si un covector indexara como una matriz de filas, creo que obtendríamos el mismo resultado para lo anterior debido a la indexación lineal.

Y +1 para ver a los covectors relacionados con el álgebra lineal.

Pero no hay razón para que la concatenación con SymTridiagonal no deba definirse, ¿verdad?

La indexación lineal de

Pensé que la indexación lineal se trataba de un orden de almacenamiento, y realmente no hay otro orden sensato para la indexación lineal de un vector de todos modos, ¿verdad? (Y perdón por la falta de ortografía).

No hay un orden transversal sensible único en la memoria. Incluso para las matrices de Fortran, puede optar por almacenar los elementos en la columna principal, fila principal o incluso en orden inverso de la columna principal (que es exactamente lo que hizo el compilador IBM Fortran I original). Además, hay otras estructuras de datos (ver # 10064) como los intentos que se pueden usar como matrices y tienen aún más opciones para el orden transversal.

Lo mismo podría decirse de los vectores y las matrices. Dado que la indexación lineal accede a los elementos en el mismo orden para una matriz de columna y su transposición (y el mismo que para un vector de columna), ¿por qué debería ser diferente un covector? Si fuera diferente, creo que debería ser que no definiríamos la indexación para los covectors en absoluto.

@toivoh sí, las mismas definiciones son

La indexación de objetos Transpose podría no estar permitida. No digo que tengan que ser indexables, pero si lo son, no necesariamente tienen que tener el mismo comportamiento de indexación. Para ser conservadores, podemos dejar la indexación sin definir por ahora y ver si aparece algún caso de uso.

Muchas implementaciones puras de Julia de funciones de álgebra lineal querrán indexar en una transposición, ¿no? Escribir la multiplicación de matrices de Julia pura (para tipos de números que no son BLAS) se vuelve fácil si no tiene que distinguir entre ningún caso posible (normal, transpuesta, ctranspuesta, conj?) Para las dos matrices involucradas, pero simplemente trátelas como matrices ordinarias. Se podrían intentar métodos ajenos a la caché para obtener un patrón de acceso a la memoria algo local.

Bien, duh.

@Jutho : Estoy de acuerdo. Y para encajar allí, los covectors deberían indexar como matrices de filas, ¿verdad?

@toivoh , si te refieres a que deben tener un índice extra 1 al frente, no estoy de acuerdo y no veo cómo eso implica mi declaración. Solo estaba hablando de productos matriciales. Matrix * vector o covector * matrix son métodos diferentes que requieren diferentes definiciones de función, no solo porque tienen un patrón de acceso a memoria diferente, sino también porque tienen un tipo de retorno diferente (Matrix_vector = vector o covector_matrix = covector), por lo que hay un razón muy práctica en Julia para no mezclar estas cosas.

En general, no soy un gran admirador de la capacidad de agregar índices adicionales 1 al indexar una matriz N-dimensional, o de, por ejemplo, el alias de tipo VecOrMat . Esto permite una programación descuidada, pero por eso también facilita cometer errores o detectar otros errores más lentamente. Solo veo dos formas útiles de crear una matriz N-dimensional, es decir, con índices N exactos, en caso de que lo esté usando como un objeto multilineal, o con un índice lineal, cuando lo esté tratando como un vector en un producto tensorial. espacio (por ejemplo, para agregar dos de estos arreglos o multiplicarlo con un escalar). Si bien eso es suficiente para mi uso, puedo aceptar que es demasiado limitado para otros.

@Jutho : Ok, estoy de acuerdo en que probablemente no importe ya que el tipo de retorno tiene que ser diferente de todos modos.

Aquí hay un intento de describir lo que estamos tratando de hacer y dar algunos axiomas:

Creo que hemos llegado a un consenso bastante claro de que el punto de partida es el álgebra matricial (que se basa únicamente en matrices). Para la mayoría de las operaciones, sabemos cómo se comportan en la configuración de matriz pura.

Creo que lo que estamos tratando de hacer es extender y refinar la configuración de la matriz pura de manera consistente para tener también escalares y vectores verdaderos, y porque parece ser necesario para la coherencia, covectors.

Aquí está mi punto de vista de los escalares y los vectores (visto desde el punto de vista de la matriz pura): Un escalar es una matriz, que por su tipo está restringida a 1 x 1. Un vector es una matriz, que por su tipo está restringida a be nx 1. (A continuación, argumentaré que un covector es una matriz que por su tipo está restringida a ser 1 x n.) Desde este punto de vista, podemos dar dos axiomas: (no muy formalmente descrito a continuación)

  • Extensión: considere una función de matrices a matrices. Si siempre producirá una matriz de salida con un tamaño de 1 en una dimensión dada, dadas las entradas de ciertos tipos, ese hecho se codificará en el tipo de salida (haciéndolo un escalar / vector / covector).
  • Refinamiento: si una función que tomaría un argumento de matriz en la configuración de matriz pura requiere que la entrada tenga un tamaño de uno en una o más dimensiones, puede negarse a aceptar una entrada donde este hecho no está codificado en el tipo.

Si estamos de acuerdo con lo anterior, la transposición de un vector debe ser el tipo de covector descrito anteriormente: La transposición de una matriz ans 1 produce una matriz 1 xn. Si codificamos el hecho de que el tamaño a lo largo de las primeras dimensiones del resultado es siempre uno, obtenemos el covector como se describe anteriormente.

Si comienza desde el punto de vista del álgebra matricial, entonces lo que dice es correcto. Este es el modelo que MATlab probablemente implementa perfectamente. Todo es una matriz. Es un sistema cerrado, todas las operaciones sobre matrices producen nuevas matrices.

Ciertamente tuve la impresión de que el objetivo de este problema (tomar en serio las transposiciones vectoriales, ya que no son solo matrices) era alejarse de ese modelo de álgebra matricial, porque comienza a mostrar inconsistencias si desea separar números de matrices 1x1 por razones de eficiencia. La alternativa es entonces seguir el modelo de álgebra lineal , donde hay una clara distinción entre el campo (escalares), el espacio vectorial (y su dual correspondiente) y el espacio de operadores / transformaciones lineales (matrices).

Creo que las operaciones de álgebra lineal en Julia están fuertemente arraigadas en la tradición de matlab, con algunas excepciones notables como tener escalares y vectores, y no intentar adivinar al usuario. Cualquier cosa que se aleje demasiado de eso probablemente sea una gran interrupción.

Creo que mis axiomas anteriores deberían ayudar a resolver las inconsistencias que aparecen cuando desea separar escalares y vectores de matrices (por razones de eficiencia y corrección). Pero definitivamente estoy abierto a escuchar sobre otros posibles sistemas.

Estoy de acuerdo con @Jutho aquí; El objetivo de este problema es alejarse de la semántica de MATLAB de "todo es una matriz". La regla de MATLAB "puede agregar dimensiones de singleton finales" es necesaria para que el universo se cierre bajo operaciones de álgebra lineal, pero esa regla define clases de equivalencia que contienen miembros del tipo T y otros del tipo Array{T,N} para all N , y es la razón principal por la que el sistema de tipos de MATLAB es indecidible. (Véase el teorema 1 de Joisha y Banerjee, 2006 ; aunque el resultado se expresa en términos de formas, el problema realmente es cómo cambiar el rango de la matriz puede cambiar la semántica del programa).

Pero sigo pensando que tuvimos un consenso bastante bueno sobre que la multiplicación de escalares, vectores, covectores y matrices debería ser asociativa (con la excepción de cosas como (v'*v)*v donde dos no escalares se multiplican para producir un escalar), y que por ejemplo, v'*M*v debería producir un escalar, M*v un vector y v'*M un covector. No estoy seguro de cuánto más es posible alejarse de la semántica de todo es una matriz mientras se cumplen estas condiciones.
¿Qué más estamos tratando de evitar y qué propiedades nos gustaría obtener en su lugar?

¿Cuánto peor sería si solo fusionáramos T y Array{T,0} en algunos casos? (Por ejemplo, indexación: M[:,2] produce un Array{T,1} , pero M[2,2] no produce un Array{T,0} )

Aún podemos mantener una semántica válida con Transpose{Vector} .

Releí todos los comentarios sobre la asociatividad y concluí que la mayor parte de esa discusión no se trata en realidad de la asociatividad per se, sino más bien de la semántica de los productos internos y externos. Si encuentra una expresión que no se refiera a ninguno de los dos, indíquelo.

El problema con la semántica de tipo Matlab es que M*v' y v*M veces funcionan aunque no deberían. Si M es m x 1 entonces M*v' es válido y devuelve una cantidad externa similar a un producto (ya que v' es 1 x n ). De manera similar, si M es 1 x m y tenemos la regla "puede agregar singletons finales", entonces v*M se puede evaluar como el producto de n x 1 y 1 x m matrices, de nuevo produciendo una cantidad externa similar a un producto.

La cuestión de combinar T y Array{T,0} también se planteó en la literatura de APL: en APL, las matrices multidimensionales están anidadas recursivamente, lo que plantea la cuestión de si Array{T,0} y T son distinguibles. De lo contrario, son "matrices conectadas a tierra" (que se repiten hasta T ), de lo contrario son "matrices flotantes" (que recurren a Array{T,0} solamente). Creo que More, 1973 en realidad demostró que cualquiera de las dos opciones es axiomáticamente coherente. No estoy seguro de si APL resolvió alguna vez la cuestión de cuál usar antes de que la mayoría de los profesionales se retiraran o pasaran a otra cosa.

@jiahao : No me di cuenta de lo fundamental que era tu observación de que

v[i] = e_i' * v

para unir el álgebra lineal y la semántica de indexación. Pero luego también debes considerar

M[i,j] = e_i' * M * e_j

lo que indica que un producto interno con un vector base desde la derecha corresponde a la indexación a lo largo de la segunda dimensión. Por lo tanto, mantendría que la entrada i th de un covector v' debe indexarse ​​como

v' * e_i = v'[1, i]

donde, por supuesto, nos gustaría escribir algo más que 1 como primer índice, pero ¿qué?
De todos modos, ya que permitimos

e_i' * v = v[i] = v[i, 1]

entonces 1 debería permitirse como marcador de posición también en el caso anterior.

v' * e_i embargo, e_1' * (v' * e_i) es un covector, no un escalar. Entonces las dimensiones no coinciden con pensar en v'[1, i] = e_1' * v' * e_i

editar: ¿Creo que este podría ser un argumento en contra de permitir los singleton finales en la indexación?

Sí, la indexación matricial es el siguiente paso lógico, y e_i' * M * e_j es en realidad una de las expresiones donde el axioma de asociatividad se vuelve útil, ya que

(e_i' * M) * e_j = m_i' * e_j

e_i' * (M * e_j) = e_i' * m_j

debe ser igual. La indexación matricial se puede derivar de una regla de indexación vectorial y una regla de indexación de covector.

Creo que una resolución coherente de este problema puede requerir expresiones como v[i, 1] que no se permitan, ya que la regla que permite este comportamiento de indexación
a) debe causar casos espurios para que funcionen A*v' y v*A (el primero lo hace pero el segundo no porque aplicamos la regla de singleton final de manera inconsistente), y
b) si consideramos la igualdad de v[i] = v[i, 1, 1, 1] entonces la regla de indexación de covector correspondiente se vería como (v')[1, 1, 1, i] y uno tiene que tener la regla correspondiente de "permitir un número arbitrario de singletons iniciales" para covectors por coherencia. Encuentro muy inquietante la falta de una primera dimensión definida de manera única.

No creo que este razonamiento del índice vaya a ninguna parte. La indexación es una propiedad general de las matrices dimensionales N , y que se pueden escribir como expresiones matriciales simples para N=1 o N=2 es bastante trivial y no contiene ningún elemento fundamental. información. Pero esto no se generaliza a matrices de rango superior y, por lo tanto, no debe usarse para motivar si un covector necesita un 1 inicial o no cuando se indexa. Esto conduce rápidamente a inconsistencias como se observó en las publicaciones anteriores.

Como se indicó anteriormente, nunca he sido un fanático de confiar en los índices de seguimiento y no pude pensar en una sola situación en la que sea necesario o incluso realmente útil. Pero puedo aceptar que mi punto de vista es demasiado limitado.

No he digerido completamente todo esto, pero mi pregunta principal es simplemente: ¿ size(covector) igual a (n,) o (1,n) ?

Si no son parte de la familia AbstractArray , ni siquiera se requiere estrictamente que se defina size .

Aunque por razones prácticas supongo que se definirá (como lo es para los números, etc.), y mi voto va a (n,) . Desde el punto de vista del almacenamiento / contenedor, diría que no hay distinción entre vectores y covectores. Por lo tanto, tampoco es necesario usar covectors como contenedores y, por lo tanto, no pertenecen a la jerarquía AbstractArray . Es solo un tipo de envoltura simple para expresar que se comportan de manera diferente a los vectores con respecto a las operaciones de álgebra lineal.

"Mientras el álgebra y la geometría han estado separadas, su progreso ha sido lento y sus usos limitados; pero cuando estas dos ciencias se han unido, se han prestado fuerzas mutuas y han marchado juntas hacia la perfección". - Joseph Louis Lagrange

Desearía poder contribuir más, pero pensé que solo diría que estoy a favor de tener un sistema que favorezca que las sofisticadas herramientas matemáticas empleadas por físicos e ingenieros sean intuitivas y precisas. Quizás, este recurso del MIT, podría ser de utilidad ...

http://ocw.mit.edu/resources/res-8-001-applied-geometric-algebra-spring-2009/lecture-notes-contents/

De hecho, ahora que lo pienso, es más justo decir que

e_i' * x = x[i, :] # x is a vector or matrix
x * e_j  = x[:, j] # x is a covector or matrix

Entonces para la indexación matricial tendríamos

e_i' * M * e_j = e_i' * (M * e_j) = e_i' * M[:, j] = M[:, j][i, :] = M[i, j]
e_i' * M * e_j = (e_i' * M) * e_j = M[i, :] * e_j  = M[i, :][:, j] = M[i, j]

Actualmente, esto no se cumple en Julia, por ejemplo, v[i, :] actualmente produce una matriz 1x1 y no un escalar. (Pero tal vez no debería)
La asociatividad de la multiplicación de matrices en e_i' * M * e_j corresponde a la conmutatividad de cortar a lo largo de diferentes dimensiones M[:, j][i, :] = M[i, :][:, j] , que parece ser una característica deseable.
Por la línea anterior de razonamiento, deberíamos tener

v'[:,i] = conj(v[i])

@Jutho : Creo que este paradigma de "indexación como corte repetido" se generaliza a matrices / tensores de dimensiones superiores: aplica un corte para cada dimensión del índice, en cualquier orden. Esto corresponde a una serie de contracciones con tensores de primer orden correspondientes a e_i , etc., también en cualquier orden (siempre que lleve un registro de las dimensiones que coinciden).

Creo que este paradigma de "indexación como corte repetido" se generaliza a matrices / tensores de mayor dimensión: se aplica un corte para cada dimensión al índice, en cualquier orden. Esto corresponde a una serie de contracciones con tensores de primer orden correspondientes a e_i, etc., también en cualquier orden (siempre y cuando lleve un registro de las dimensiones que coinciden).

Sí, estoy muy familiarizado con las contracciones tensoras, etc., y de hecho, obtener un elemento de matriz / elemento tensorial corresponde a tomar un valor esperado en la base computacional estándar, es decir, contraer con algunos vectores base (siempre que tenga una base ortogonal al menos ), pero no existe una asociación única en cuanto a si un índice corresponde a una contracción con e_i o con e_i' . Eso corresponde a decidir si el índice correspondiente aparece en posición covariante o contravariante, y no hay decisión única. Todas las combinaciones posibles de índices superiores e inferiores tienen aplicaciones útiles. Pero contratar con e_i y e_i' ni siquiera es la forma correcta de plantear esta pregunta desde el punto de vista matemático, porque, como he dicho anteriormente, en realidad no existe un mapeo matemático como la transposición de un vector. La transposición es una operación definida para mapas lineales (matrices) e incluso allí solo corresponde a la transposición de la matriz si también escribe vectores duales como vectores columna. La transposición de un vector es solo un truco de conveniencia que se ha introducido en el álgebra matricial (donde de hecho los vectores son matrices n x 1 ) y solo funciona porque estás en un espacio euclidiano donde hay un mapeo canónico de (conjugate ) espacio vectorial al espacio dual.

En particular, si desea las propiedades que describe arriba, debe tener que M[i,:] devuelva un objeto diferente (ya sea una matriz 1xn o un covector) y luego M[:,i] (que debería ser una matriz o vector nx1 ). El hecho de que esto no se generalice claramente a dimensiones superiores es exactamente uno de los principales puntos de discusión de este tema, y ​​parece que la mayoría de la gente está a favor de la indexación de APL, donde las dimensiones indexadas con un número se eliminan (es decir, tanto M[:,i] y M[i,:] producen una matriz de rango 1, por lo tanto, un vector). La indexación es una propiedad de las matrices, y es esta mezcla de comportamiento de indexación con operaciones de álgebra lineal lo que resulta en todas las confusiones / inconsistencias en primer lugar. Puede ser consistente siempre que permanezca dentro del ecosistema cerrado de objetos de rango N=2 , es decir, todo es una matriz, también vectores y números, y nunca considera matrices de dimensiones superiores.

También me di cuenta de que M[i,:] tendría que producir un covector según mi razonamiento anterior, como dices. Por lo tanto, parece que tener covectors es, en algún nivel, fundamentalmente incompatible con la indexación de APL. Si favorecemos la indexación APL (y me gusta), la pregunta es dónde trazar la línea entre ese mundo y el mundo donde viven los covectors. (Tenía la esperanza de que sería posible reconciliar los dos, ¿tal vez el resto de ustedes ya se habían dado cuenta de que tendríamos que renunciar a eso?)

Este conflicto tal vez no sea tan sorprendente si lo piensas:

  • En la indexación de APL, solo las dimensiones significativas e indexables se mantienen en el resultado; el resto se exprime.
  • Si hiciéramos eso con v' entonces tendríamos v' = conj(v) . En cambio, se puede considerar que el covector realiza un seguimiento de una primera dimensión que falta.

Supongo que un buen comienzo sería restringir los covectors tanto como sea posible, básicamente simplemente definiendo multiplicación, suma / resta y división a la izquierda en ellos.

La idea parece ser no hacerlos <: AbstractArray . ¿Tendría sentido que fueran subtipos de un nuevo tipo LinearOperator ? (Sé que ya se ha debatido antes, pero no recuerdo bien las conclusiones).

Creo que la ausencia de herencia múltiple o una construcción de lenguaje para interfaces (las cuales han sido discutidas) requiere que ciertos conceptos solo sean implementados por una interfaz "implícita", es decir, un conjunto de métodos que necesitan ser definidos. Los iteradores es uno de esos conceptos, los operadores lineales probablemente serían otro que debe especificarse mediante un conjunto de métodos en lugar de una jerarquía de tipos. Sería extraño tener un tipo abstracto LinearOperator y luego no tener Matrix para ser un subtipo del mismo.

@toivoh Esa es una consideración importante, razón por la cual sugerí nuevas funciones como row(M,i) y col(M,i) para extraer el vector o covector i th de una matriz. Estas funciones serían solo para matrices bidimensionales M y para personas interesadas en el álgebra matricial. Al principio puede parecer un poco menos obvio que la indexación de estilo MATLAB, pero, en general, separar conceptualmente la idea de un vector y su dual / transpose / covector ayuda a aclarar las cosas. En el caso de la mecánica cuántica, todo un campo saltó a la notación bra-ket de Dirac simplemente porque esta distinción entre vector y covector es tan crítica para vectores complejos, y la notación de Dirac lo hace obvio. Tengo la esperanza de que Julia también lo haga, además de ser una buena herramienta para matrices de almacenamiento de mayor dimensión y álgebra lineal de mayor dimensión. (Porque somos codiciosos, ¿verdad?)

Debo decir que una persona que viene con experiencia en MATLAB y algo de familiaridad con matrices en su mayoría reales (pero no es un fanático del álgebra lineal de núcleo duro) puede que al principio no se dé cuenta de por qué lo que algunos de nosotros estamos defendiendo es importante, pero yo creo que lo es.

Lo dije antes, pero lo repetiré: desde mi punto de vista tenemos esta lista de prioridades, con causalidades que fluyen hacia abajo:

(1) Las matrices son fundamentalmente contenedores de almacenamiento que utilizarán todos los usuarios de Julia, y queremos la mejor semántica para ello. Las reglas de estilo APL me parecen, de todos modos, una muy buena solución. Por otro lado, las matrices bidimensionales mínimas de estilo MATLAB con suposiciones con índices unidimensionales finales parecen antinaturales, confusas y, como dijo Jutho, incluso pueden conducir a una programación descuidada en la que no se realiza un seguimiento adecuado de los dimensión de su matriz. Me atrevería a decir que para el vector v , el código v[i,:] debería arrojar un error ya que v es unidimensional. Las operaciones basadas en elementos como + y .* son útiles para cualquier clase de contenedor, no solo en álgebra matricial o multilineal. La única concesión que las personas hacen en contenedores de almacenamiento para las cosas a continuación son los puntos adicionales en .* etc.

(2) La mayoría, pero no todos, los usuarios de Julia usarán álgebra matricial, por lo que agregamos algo de azúcar sintáctico para algunas operaciones de vectores y matrices, principalmente con el símbolo * (pero es posible que tengamos división matricial, etc.). En este sistema, tomamos matrices de una y dos dimensiones que son vectores de columna y matrices, respectivamente. Para un sistema matricial completamente funcional, también necesitamos vectores de fila (covectores) y una operación de transposición ' . Un vector de fila es en todos los sentidos posibles una matriz unidimensional, y afirmo que debería indexarse ​​como tal (¡y ciertamente no como covec[1,i] !!) Con las reglas APL, nos vemos obligados a hacer algunos distinción entre vectores y covectores, y el sistema de tipos es ideal para esto (recuerde que tuvimos la suerte de poder reutilizar la matriz y el vector como alias de tipos de matrices unidimensionales y bidimensionales en lugar de un tipo de envoltura ... en principio podríamos envuélvalos también, pero no veo el punto). Con el sistema de tipos, el compilador puede calcular que CoVector * Vector es escalar y Vector * CoVector es una matriz, y así sucesivamente. Como dijo Toivoh, _desde el punto de vista de MATLAB, el CoVector está siguiendo exactamente la pista de la primera dimensión "faltante". Los usuarios ocasionales no necesitarán construir estos objetos; solo ingresarán vectores y matrices y usarán las operaciones * y ' . Las personas que se preocupan notarán y apreciarán la distinción. El cambio _más grande_ para los usuarios es la necesidad de cambiar M[i,:] para usar una nueva función row(M,i) o convertir a la clase contenedora con algo como Transpose(M[i,:]) o M[i,:]' - uno puede pensar en esto como una ventaja, porque al igual que la notación de Dirac, nunca se olvida qué objetos son vectores y cuáles son covectores, y la asignación a objetos con tipo afirma generará errores cuando sea apropiado. Sin embargo, creo que vale la pena debatir esta notación. En el futuro, es posible que incluso ampliemos el sistema de envoltorios para permitir una evaluación diferida eficiente. Es un ganar-ganar para todos, por lo que puedo ver.

(3) Algunos de nosotros estamos interesados ​​en el álgebra multilineal, y tuvimos que aprender la diferencia entre un espacio dual y una transposición, etc. Jutho ha mencionado esto. Los arreglos de Julia ya son excelentes dispositivos de almacenamiento para tensores de mayor dimensión, y personas como nosotros usarán paquetes adicionales (que pueden o no encontrar allí el camino hacia Base). No necesitamos, ni queremos, operaciones como ' o * definidas en matrices de dimensionalidad mayor que dos. No puedo ver un caso de uso orientado al almacenamiento para ' que no se pueda hacer de manera más limpia reordenando explícitamente los índices. La idea de rastrear índices unidimensionales solo hace que el concepto sea menos atractivo ... así que por favor mantenga ' solo para matrices unidimensionales y bidimensionales, como MATLAB :) (mira, tengo cosas buenas para decir sobre MATLAB ...)

Para un sistema matricial completamente funcional, también necesitamos vectores de fila (covectores) y una operación de transposición '.

Esta declaración realmente llega al corazón del problema. Los productos de indexación, transposición y * están todos entremezclados. Además, no es posible reconciliar la semántica completa de los vectores de fila con la de los covectors sin introducir una función como row(A, i) para devolver la fila i th de A como un covector en lugar de una matriz unidimensional. Actualmente, A[1, :] quiere significar tanto "tomar la primera fila de A" como "tomar la primera porción a lo largo de la segunda dimensión de A", pero estas nociones son fundamentalmente incompatibles en la propuesta del covector.

Un vector de fila es en todos los sentidos posibles una matriz unidimensional, y afirmo que debería indexarse ​​como tal.

Creo que está siendo demasiado simplista con esta declaración. La discusión anterior ha establecido con bastante claridad que si desea una semántica de álgebra lineal completa (con los productos y transposiciones correctos), entonces un vector fila no puede tener el mismo tipo que una matriz unidimensional. Primero, indexar en un covector debe devolver valores complejos conjugados, (v')[1] = conj(v[1]) , para mantener la coherencia con el producto interno dot . En segundo lugar, los covectores no son vectores, son funcionales lineales que esperan producir un producto interno con un vector. En tercer lugar, los vectores y covectores tienen diferentes comportamientos de concatenación de matrices en hcat y vcat . Por todas estas razones, un vector de fila no puede ser "en todos los sentidos posibles una matriz unidimensional".

Yo estaría a favor de deshacerse de los singleton finales: no creo que haya visto nunca código de Julia que lo haya utilizado. Originalmente iba a decir que lo necesitaríamos para arreglos de dimensión 0, pero veo que X[] funciona bien.

Creo que la indexación APL, junto con la función row(M,i) para los vectores de fila, tiene más sentido y parece un buen compromiso. No estoy seguro de la indexación de vectores de fila, pero _no_ me gusta (v')[1,i] .

La otra gran decisión, que aún no hemos mencionado, son los tipos. Mi único hallazgo después de haber probado esto antes es que es realmente difícil que v' sea ​​un AbstractVector , ya que hace que el envío sea un desastre.

Dos posibles opciones:

  1. Usamos tipos separados para matriz y vector:

    • Transpose <: AbstractMatrix

    • CoVector <: Any

    • algún tipo de transposición para Factorization objetos.

  2. Usamos el mismo tipo para todas las transposiciones, pero tenemos X' _no_ ser un AbstractMatrix

    • Transpose <: Any

Para la conjugación, podemos

a. definir ConjugateTranspose (junto con ConjugateCoVector si vamos con la opción 1 anterior)

segundo. use un tipo de contenedor Conjugate , y anide apropiadamente: necesitaríamos alguna convención sobre si usamos Transpose{Conjugate{T}} o Conjugate{Transpose{T}} .

Me gusta tener Transpose{Matrix} no un subtipo de AbstractMatrix . Creo que el análogo más cercano que tenemos en base es un tipo de matriz especial como Symmetric , que se comporta algebraicamente como una matriz pero no en su semántica de indexación. (# 987 estableció que sin inherencia múltiple o rasgos sagrados, la jerarquía de tipos tiene que respetar la semántica del contenedor sobre la semántica algebraica).

El problema de componer tipos que son básicamente "etiquetas semánticas" también apareció en # 8240. Creo que Transpose{Conjugate{T}} sería preferible, ya que la conjugación es una noción que recae en el campo subyacente de elementos.

Aquí hay ejemplos que a veces siguen la misma regla de singleton final que MATLAB, y en otras ocasiones no:

  • Los singleton finales están permitidos en las operaciones de indexación. (Como MATLAB)
julia> (1:5)[5,1,1,1,1,1,1]
5
  • Los segmentos finales no están permitidos en operaciones de indexación. (A diferencia de MATLAB, que los permite).
julia> (1:5)[5,:]
ERROR: BoundsError()
 in getindex at abstractarray.jl:451
  • Para matrices de rango> = 3, los singleton finales implícitos se agregan a una operación de asignación indexada cuando hay menos índices que el rango de la matriz y el último índice es un escalar (como MATLAB):
julia> A=zeros(2,2,2); A[1,2]=5; A #Same as A[1,2,1]=5
2x2x2 Array{Float64,3}:
[:, :, 1] =
 0.0  5.0
 0.0  0.0

[:, :, 2] =
 0.0  0.0
 0.0  0.0
  • Para las matrices de rango> = 3, los _slices_ finales implícitos se agregan a una operación de asignación indexada cuando hay menos índices que el rango de la matriz y el último índice es _no escalar_ (como MATLAB):
julia> A[:,:]=3; A
2x2x2 Array{Float64,3}:
[:, :, 1] =
 3.0  3.0
 3.0  3.0

[:, :, 2] =
 3.0  3.0
 3.0  3.0
  • Para matrices de rango> = 3, los singleton finales implícitos se agregan a una operación de indexación cuando hay menos índices que el rango de la matriz y el último índice es un escalar (como MATLAB):
julia> A=reshape(1:8,2,2,2); A[:,1]
2-element Array{Int64,1}:
 1
 2

julia> A[:,1,1]
2-element Array{Int64,1}:
 1
 2
  • Para matrices de rango r> = 3, una operación de indexación cuando hay k <r índices que el rango de la matriz y el último índice es un segmento linealiza implícitamente la matriz rk de rango restante. (como MATLAB):
julia> A=reshape(1:8,2,2,2); A[1,:]
1x4 Array{Int64,2}:
 1  3  5  7

julia> A=reshape(1:8,2,2,2); A[1,:,:]
1x2x2 Array{Int64,3}:
[:, :, 1] =
 1  3

[:, :, 2] =
 5  7
  • Los singleton finales no se eliminan en las operaciones de asignación. (A diferencia de MATLAB)
julia> A=zeros(1); A[1] = randn(1,1)
ERROR: `convert` has no method matching convert(::Type{Float64}, ::Array{Float64,2})

You might have used a 2d row vector where a 1d column vector was required.
Note the difference between 1d column vector [1,2,3] and 2d row vector [1 2 3].
You can convert to a column vector with the vec() function.
 in setindex! at array.jl:307

julia> A=zeros(1,1); A[1,1] = randn(1)
ERROR: `convert` has no method matching convert(::Type{Float64}, ::Array{Float64,1})
 in setindex! at array.jl:308
  • Julia no agrega automáticamente una dimensión singleton final cuando al hacerlo permite una operación válida. (A diferencia de Matlab, que lo hace)
julia> 1/[1.0,] #In MATLAB, interpreted as the inverse of a 1x1 matrix
ERROR: `/` has no method matching /(::Int64, ::Array{Float64,1})
  • Los productos externos funcionan y son una excepción a la regla anterior; su semántica utiliza implícitamente un singleton final en el primer argumento. (Como Matlab)
julia> [1:5]*[1:5]' # Shapes are (5,) and (1,5) - promoting to (5,1) x (1,5) works
5x5 Array{Int64,2}:
 1   2   3   4   5
 2   4   6   8  10
 3   6   9  12  15
 4   8  12  16  20
 5  10  15  20  25

Parece que estas cosas podrían merecer un poco de limpieza, una vez que hayamos concluido cómo nos gustaría que se comportaran. El comportamiento actual cuando se indexa con muy pocos índices donde el último es una porción parece particularmente sospechoso.

Guau. Los ejemplos que pone Jiahao son extraordinariamente perturbadores ... No me gusta rastrear singletons en operaciones de indexación y operaciones de asignación de indexación debido a su implícita y ambigüedad. Si no es consciente de estos comportamientos de antemano, podría terminar haciendo una cosa cuando en realidad está tratando de hacer otra. Estoy a favor de comprometerme con un uso claro y exacto del idioma y evitar atajos ambiguos.

Para implementar esto realmente, vamos a necesitar algún tipo de envío triangular, de lo contrario no estoy seguro de cómo expresarías cosas como "una matriz con entradas Complex64 o Complex128 , su transposición , o su transposición conjugada ". Especialmente si usamos las opciones 2 + b anteriores.

@ esd100 , ¿cuáles de estos son ambiguos o peligrosos? Todos estos son convenientes, cuando se imaginan "singletons virtuales" para usted y los deseaba, o inconvenientes, cuando los deseaba y no lo eran. Ninguno de estos tiene dos significados plausibles con comportamientos diferentes.

Un vector de fila es en todos los sentidos posibles una matriz unidimensional, y afirmo que debería indexarse ​​como tal.

Creo que está siendo demasiado simplista con esta declaración.

¡Cierto! Lo siento @jiahao , [] . Tiene razón en que la concatenación es una consideración importante, y creo que la forma natural de hacerlo (construir una matriz) es razonablemente obvia (al pensar que tiene un tamaño de 1 xn en este caso). Claramente, un covector no es "en todos los sentidos" un 1D Julia Array ... ese es el punto ...

Los ejemplos de jiahao también me perturban. Yo estaría a favor de eliminar cualquier posible comportamiento de singleton final. La linealización de dimensiones posteriores puede ser útil, pero también podría fomentar una especie de pereza en la que se olvida cuántas dimensiones tiene su matriz ... (Creo que esto es lo que implica "ambiguo" y "peligroso" ... Realmente quiero Julia arrojará un error cuando trato una matriz de 16 dimensiones como una matriz de 15 dimensiones, por ejemplo).

¿Cuáles de estos son ambiguos o peligrosos?

El cuarto me parece ambiguo y peligroso.

julia> A=zeros(2,2,2); A[:,:]=3; A
2x2x2 Array{Float64,3}:
[:, :, 1] =
 3.0  3.0
 3.0  3.0

[:, :, 2] =
 3.0  3.0
 3.0  3.0

Claramente (para mí) esto debería ser un error de sintaxis. A es de rango 3 y no se hizo referencia a la 3ª dimensión. Lo más probable es que la tercera dimensión se haya dejado por error y ahora se haya introducido en el código un error difícil de encontrar. Agradecería recibir un error en este punto (como ocurre tanto en IDL como en fortran).

Creo que es mejor si las matrices solo permiten la indexación con el número correcto de índices o el caso especial de indexación lineal 1D (ya que es extremadamente útil). Esto también incluiría rechazar los singleton finales.

o el caso especial de la indexación lineal 1D (ya que es extremadamente útil)

¡Con qué facilidad se venden nuestros principios! :)

Nunca me ha gustado tanto la indexación lineal; me parece un truco. A menudo, solo queremos la forma más rápida de iterar sobre una matriz. Y para todos los arreglos, excepto los simples y densos, la indexación lineal puede ser muy lenta. Dicho esto, es posible que no podamos eliminar la indexación lineal por completo (por razones de rendimiento y por romper demasiado código).

Sí, bastante cierto. Pero al menos la indexación 1D es visualmente distinta. Mientras que indexar una matriz 6D con 5D lo es mucho menos. En cualquier caso, preferiría tener reglas de indexación más estrictas y renunciar a la indexación lineal 1D que ir en la otra dirección. Especialmente porque uno puede obtener fácilmente una referencia reformada a la matriz que comparte memoria.

¿Podemos usar {} corchetes (o algún otro) para la indexación lineal y mantener [] para la indexación multidimensional? Solo una idea...

El 23 de marzo de 2015, a las 2:36 pm, Bob Portmann [email protected] escribió:

Sí, bastante cierto. Pero al menos la indexación 1D es visualmente distinta. Mientras que indexar una matriz 6D con 5D lo es mucho menos. En cualquier caso, preferiría tener reglas de indexación más estrictas y renunciar a la indexación lineal 1D que ir en la otra dirección. Especialmente porque uno puede obtener fácilmente una referencia reformada a la matriz que comparte memoria.

-
Responda a este correo electrónico directamente o véalo en GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -84805310.

También creo que sería bueno que pudiéramos separar la sintaxis
para la indexación lineal de la sintaxis de indexación normal, pero estoy de acuerdo en que
podría ser un cambio enorme.

Si bien este problema parece atraer mucho debate, la mayor parte del trabajo real para mejorar la indexación se ha realizado en un gran número de solicitudes de extracción a lo largo del ciclo 0.4. Por ejemplo, ahora que tenemos CartesianIndex y amigos, no veo por qué habría que separar la sintaxis para la indexación lineal y cartesiana; de hecho, ahora puede combinarlos (# 10524). Me gustaría deshacerme de la indexación lineal también, pero a veces es más eficiente (probablemente debido en gran parte a # 9080; enumerate y zip sufren el mismo problema). Probablemente deberíamos implementar fastindex como envoltorio alrededor de eachindex , como en # 10507.

Si está interesado en las reglas de indexación, en lugar de aporrear este tema hasta la muerte, centrémonos en la frontera más interesante. En este momento, es indiscutiblemente el # 10525. En particular, https://github.com/JuliaLang/julia/pull/10525#issuecomment -84597488 necesita algún tipo de resolución.

@timholy Realmente no he seguido todos los desarrollos en la indexación cartesiana rápida, y después de echar un vistazo a base / multidimensional.jl, veo que hay mucha metaprogramación en marcha. ¿Existe alguna posibilidad de que tenga tiempo para escribir (o dar una charla en la JuliaCon) sobre cómo funciona todo esto?

De alguna manera, no hay mucho que saber: si bien hay una buena cantidad de cosas debajo del capó, la idea es que sea muy fácil de usar. Tan literalmente

k = 0
for I in eachindex(A)
     B[k+=1] = A[I]   # B is being linearly-indexed, A is being cartesian-indexed
end

podría ser todo lo que necesitas saber. (En otras palabras, la ayuda en eachindex podría ser toda la documentación necesaria). Sin embargo, resulta que puede escribir una gran cantidad de algoritmos usando algunas extensiones de este paradigma básico; cuáles son esas extensiones y por qué son poderosas puede, de hecho, ser menos obvio.

Planeo escribir esto en algún momento de los próximos meses, pero si la gente realmente quiere conocer los detalles, quizás una charla de la JuliaCon sería razonable. @Jutho o @mbauman podrían dar esa charla tan bien como yo.

Agregaría más sobre la ambigüedad, sin embargo, creo que parte de la discusión final resumió ciertos puntos clave. Quizás soy más sensible a este tipo de ambigüedad como forastero o por miedo, a riesgo de sonar demasiado dramático. En mi humilde opinión, cualquier cantidad de ejercicios mentales sencillos y de misión crítica podrían llevarlo por un camino en el que el análisis de costo / beneficio de un simple error no vale la pena.

Probablemente deberíamos implementar fastindex como un contenedor alrededor de cada índice, como en # 10507

@timholy ¿Hay alguna razón para que eachindex devuelva un UnitRange para matrices lineales rápidas? Todavía sería de tipo estable, y si la persona que llama alguna vez quiere asegurarse de obtener un CartesianIndex, puede construir manualmente un CartesianRange (también podríamos agregar un método para eachindex(size(A)) ya que CartesianRange no se exporta ).

Eso es lo que hizo al principio, pero creo que @Jutho lo cambió. (Si eso no es cierto, entonces probablemente lo cambié sin darme cuenta). Supongo que el cambio fue motivado por el bien de la coherencia (para que pueda contar con obtener un CartesianIndex ), que tiene cierto sentido. . Pero, como señala, existen medios alternativos para garantizarlo.

@Jutho , ¿alguna idea?

No recuerdo haber cambiado eachindex , pero podría ser. Ciertamente no recuerdo una buena razón para ello, aparte de tener un resultado consistente independiente del tipo de matriz. Pero si la diferencia entre matrices con y sin indexación lineal eficiente se explica claramente en los documentos, no veo ninguna razón por la que eachindex no pueda devolver un rango lineal en el caso de matrices con indexación lineal eficiente.

@timholy , en respuesta a tu publicación anterior, no puedo asistir a la JuliaCon, así que siéntete libre de hablar sobre las cosas de CartesianIndex (de todos modos, solo contribuí con algunos toques finales). Ya estoy esperando los informes y videos de la conferencia.

Un pensamiento caprichoso: para una matriz A de cualquier dimensión (incluida la dimensión> 2), uno podría tener A' permutar cíclicamente los índices de A . Entonces, A'[i, j, k] == A[j, k, i] . Esto se reduciría a la transposición de matriz habitual para matrices 2d, así como a la transposición habitual para "vectores" de fila y columna cuando se construye como en MATLAB (es decir, como matrices [n, 1] y [1, n] 2d respectivamente). Por lo tanto, nunca mapearía un "vector" de columna 2d con un covector verdadero o un "vector" de fila 2d con un vector verdadero. (Creo que esta propiedad es buena, pero otros pueden no estar de acuerdo.) Daría las identidades A'' == A' y v'' == v _para matrices y vectores construidos como objetos de matriz_. Dado el tipo de jerarquía de tipos como se discutió anteriormente (donde los vectores y covectores verdaderos se distinguen lo suficiente de las matrices abstractas), ' todavía podría recibir un método completamente diferente para los vectores y covectores verdaderos donde corresponde al concepto algebraico lineal y no necesita satisfacer v'' == v (pero podría si eso es lo que la gente decide que quiere).

Para ser claros: no creo ni por un segundo que ' _necesita_ tener ningún método para matrices de dimensión> 2, pero no había visto esta propuesta anterior (a menos que eso sea lo que significa "índices reversibles ") y pensé en mencionarlo. El mayor daño que veo que hace (prima facie) es conceptual: combinar una operación combinatoria (ligeramente arbitraria) con un operador normalmente tomado como del dominio del álgebra lineal. A esto se puede responder que al menos cierto grado de tal fusión es inevitable cuando intentamos extender conceptos algebraicos lineales en conceptos puramente centrados en datos (como lo demuestra toda esta discusión). Como tal, también podemos obtener una abreviatura conveniente para las permutaciones cíclicas de matrices multidimensionales como un subproducto de determinar lo que ' debería hacer en general para las dimensiones de matrices multidimensionales, siempre que se reduzca al caso esperado en 2 dimensiones. Incluso se podría agregar un método que tome un argumento entero para que A'(k)[I] permuta cíclicamente los índices de A[I] k veces, dando A'(ndims(A))[I] == A[I] y A'(-k)[I] permuta los índices en la dirección opuesta.

Solo un pensamiento.

Si transpose se define para matrices multidimensionales, preferiría que aún satisfaga A''=A , es decir, es su propia inversa. Esto está en conflicto directo con la propuesta anterior.

Eso es justo. Como dije, mi sugerencia solo es (potencialmente) atractiva si uno se siente cómodo dejando crecer una brecha entre el significado algebraico lineal de la transposición y cualquier significado centrado en la matriz que se imponga en el caso d> 2. Mi razonamiento fue que mientras esa brecha ya esté (algo así) sucediendo, y si los métodos simplemente no se usarían por completo de lo contrario, podría ser bueno tener una abreviatura para permutar índices, siempre y cuando no requiera ningún tratamiento especial para que funcione el caso d = 2. Como usted (@Jutho) mencionó, es una coincidencia conveniente que la transposición de dimensiones en el caso 2d tenga el significado algebraico lineal que tiene (y solo después de identificar (co) vectores como matrices 2d, para el caso), así que tal vez no No es necesario ser exigente con las propiedades matemáticas de transpose (por ejemplo, requerir A'' == A ) para d> 2. Si uno desea seguir esta ruta, hay muchos métodos potencialmente útiles que podrían ser asignado, por ejemplo: A' permuta cíclicamente una vez, A'(k) permuta cíclicamente k veces, A'(I) por I::Array{Int, 1} con longitud <= ndims(A) cíclicamente permuta los índices enumerados en I , y A'(p) por p::Permutation de longitud <= ndims(A) permuta índices de acuerdo con p . Pero supongo que quizás lo mejor que se puede hacer es crear un paquete y ver si es lo suficientemente útil como para ponerse al día.

Apoyo dos cambios / aclaraciones que se han mencionado, por ejemplo, por StefanKarpinski el 16 de octubre de 2014 y simonbyrne el 22 de marzo de 2015, pero con una estipulación:

  1. transpose solo debe usarse para vectores y matrices bidimensionales, no para tensores generales.
  2. Los operadores * y transpose deben eliminar las dimensiones finales de singleton al final del cálculo. De lo contrario, se pueden conservar las dimensiones finales de singleton.

Estos dos cambios proporcionarían muchas comodidades y resolverían muchas ambigüedades dentro del trabajo de álgebra lineal tradicional. Intencionalmente no dicen nada sobre la indexación general de matrices o el álgebra tensorial, que pueden considerarse por separado. (En particular, la transposición de tensor debe considerarse una operación completamente separada de la transposición de matriz / vector).

Según esta propuesta, cuando se trabaja con multiplicación y transposición de matrices, no habría esencialmente distinción entre un vector y una matriz de una columna, ni habría distinciones significativas entre un escalar, un vector de longitud 1 y un 1 por -1 matriz. Sin embargo, x' sería una matriz de 1 por n, no un vector.

Por otro lado, con el fin de almacenar datos, se podría construir una matriz de cualquier tamaño. No se eliminarían dimensiones únicas porque los conceptos del álgebra lineal de multiplicación y transposición no estarían involucrados.

A continuación se muestra una transcripción hipotética de una sesión de Julia con los cambios propuestos.

Primero, definimos un escalar, dos vectores y una matriz.

julia> alpha = 2.0
2.0

julia> x = [1.0; 2.0; 3.0]
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

julia> y = [4.0; 5.0; 6.0]
3-element Array{Float64,1}:
 4.0
 5.0
 6.0

julia> A = [1.0 2.0 3.0; 4.0 5.0 6.0; 7.0 8.0 9.0]
3x3 Array{Float64,2}:
 1.0  2.0  3.0
 4.0  5.0  6.0
 7.0  8.0  9.0

La multiplicación de vectores escalares funciona incluso si existen dimensiones extrañas y el resultado es siempre un vector.

julia> alpha*x
3-element Array{Float64,1}:
 2.0
 4.0
 6.0

julia> alpha*x[:,[1]]
3-element Array{Float64,1}:
 2.0
 4.0
 6.0

Transponer es una involución.

julia> x'
1x3 Array{Float64,2}:
 1.0  2.0  3.0

julia> x''
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

julia> x==x''
true

julia> x'''
1x3 Array{Float64,2}:
 1.0  2.0  3.0

Multiplicar una matriz por una matriz de una columna produce un resultado idéntico al producto matriz-vector.

julia> A*x
3-element Array{Float64,1}:
 14.0
 32.0
 50.0

julia> A*x[:,[1]]
3-element Array{Float64,1}:
 14.0
 32.0
 50.0

Una matriz de una fila multiplicada por una matriz equivale a una matriz de una fila.

julia> x'*A
1x3 Array{Float64,2}:
 30.0  36.0  42.0

El producto interno es un escalar y se obtiene mediante las reglas más generales para los productos matriz-vector y matriz-matriz.

julia> x'*y
32.0

julia> x'*y[:,[1]]
32.0

El producto exterior no es nada especial.

julia> x*y'
3x3 Array{Float64,2}:
  4.0   5.0   6.0
  8.0  10.0  12.0
 12.0  15.0  18.0

julia> x[:,[1]]*y'
3x3 Array{Float64,2}:
  4.0   5.0   6.0
  8.0  10.0  12.0
 12.0  15.0  18.0

Un vector multiplicado por un vector es un error.

julia> x*y
ERROR: `*` has no method matching *(::Array{Float64,1}, ::Array{Float64,1})

Un vector multiplicado por una matriz es un error.

julia> x*A
ERROR: DimensionMismatch("*")
 in gemm_wrapper! at linalg/matmul.jl:270
 in * at linalg/matmul.jl:74

La multiplicación de matrices es asociativa.

julia> (x*x')*y
3-element Array{Float64,1}:
 32.0
 64.0
 96.0

julia> x'*y
32.0

julia> x*(x'*y)
3-element Array{Float64,1}:
 32.0
 64.0
 96.0

julia> norm((x*x')*y-x*(x'*y))
0.0

EDITAR: Se eliminaron dos ejemplos que implican la degradación de la dimensión interna en un producto. No influyeron mucho en la discusión actual.

Me temo que esto perdería la estabilidad de tipo de las operaciones involucradas, ya que descartar las dimensiones finales de singleton no es una operación de tipo estable.

A mi modo de ver, todo el asunto del covector se trata de tener una dimensión singleton que se conoce en función del tipo que se eliminará. Además, nos hemos esforzado bastante por preservar la distinción entre un vector y una matriz que tiene una sola columna, pero esta sugerencia eliminaría esa distinción en algunos casos, como x'' .

Sugerencia herética: ¿qué pasa si descartamos la dimensionalidad de los parámetros de tipo y realizamos todas las comprobaciones de dimensionalidad / tamaño en tiempo de ejecución? (Terminamos haciendo una buena cantidad de eso de todos modos). Y deje el parámetro de dimensionalidad para los tipos que también codifican el tamaño como parámetro de tipo. (Se agacha, se esconde durante dos semanas y cambia su cuenta de github a @SomeoneWhoCertainlyIsntTimHolyUhUhNoWay).

(Por supuesto, realmente me quejaría de mí mismo solo por el número 10525).

FWIW, diría que no es una idea tan loca: así es como funciona Torch, por ejemplo, y los desarrolladores de Torch han expresado su descontento con la codificación de dimensionalidad de Julia dentro del sistema de tipos.

@timholy Pregunta: ¿podríamos usar esto para mejorar la semántica con respecto al álgebra lineal y las transposiciones de vectores?

Si uno todavía estuviera interesado en usar CoVector / DualVector / TransposeVector estos aún tendrían que envolver nuestro nuevo TimHolyArray{DataType} _y_ tendríamos que entender la transposición de una matriz de dimensión mayor que dos (o uno), o bien prohibir la construcción TransposeVector(tharray) cuando tharray tiene una dimensión mayor que dos (o uno) ... De hecho, todo tipo de las cosas tendrán que dar errores a nivel de tiempo de ejecución que actualmente son errores de tiempo de compilación (como multiplicaciones que actualmente no están definidas y por lo tanto prohibidas por el sistema de tipos).

Por otro lado, implementar una bandera de transposición dentro de esta nueva clase puede ser malo ... agrega más complejidad a lo que debería ser un contenedor eficiente y liviano. Yo tendería a descartar esa opción y dejar el trabajo duro al compilador / sistema de tipos.

No estoy necesariamente en contra de su idea, pero parece ser un problema adicional que podría hacerse en paralelo a las transposiciones vectoriales.

@timholy : Estoy realmente seguro de si este es el camino a seguir o no. Hay situaciones en las que me resulta muy útil enviar sobre la dimensión.

La sugerencia fue que esto se aplicara a todas las matrices. Pero ahora estoy en contra de mi propia propuesta, simplemente porque para muchos casos multidimensionales es necesario generar N bucles para matrices dimensionales N . No podríamos hacer eso más si N no fuera un parámetro de tipo.

Sí, ¿no es este el objetivo de sus macros cartesianas (o la función por etapas a la que todavía no estoy acostumbrado :-))?
Hice cosas similares en C ++ donde es un verdadero dolor tener la dimensión como parámetro de plantilla. Pero tuve situaciones en las que la variante dinámica era limitante porque se necesitaban declaraciones if grandes para especializar las diferentes dimensiones de la matriz.

Propuesta:

  1. Cambie el nombre del comportamiento de multiplicación de matrices actual a timesfast y el comportamiento de transposición actual a transposefast .
  2. Modifique (*) y transpose para truncar las dimensiones finales de singleton como en el comentario anterior . Por ejemplo, u'*v se convertiría en un escalar, v'' se convertiría en un vector y (v*v')/(v'*v) se definiría.

El comportamiento existente es de tipo estable. El comportamiento propuesto sigue las convenciones de muchos textos de álgebra lineal. Ambos son valiosos. Quizás Julia debería tener ambos.

Quiero usar a Julia en el aula, así que voto por el comportamiento predeterminado para valorar la conveniencia sobre la eficiencia.

¡Gracias a varios colaboradores por ponerme al día con este hilo tan largo!

@briansutton : Creo que realmente deberías reconsiderar lo que estás pidiendo. Le animo a que espere hasta que tenga una comprensión más profunda de cómo trabaja Julia antes de proponer que redefinamos el significado de multiplicación.

Este problema ha sido sobre cómo podemos evitar tantos casos especiales como sea posible.

Las reglas que permiten modificar las dimensiones singleton se rompen en el momento en que una de las dimensiones singleton es una que realmente le importa. Con la regla "truncar [todas] las dimensiones finales de singleton", entonces si v es un vector de 1 elemento, entonces v' es un escalar, y entonces v'' escalar. Entonces, incluso en esta propuesta, la propiedad que v == v'' no siempre se mantiene.

Puede intentar modificar la regla para "truncar solo la última dimensión singleton final si la matriz tiene dimensionalidad 2". Pero incluso con esta regla modificada, el producto externo v * w' no se sigue automáticamente de la definición de multiplicación de matrices, sino que tiene que ser su propia definición en caja especial de Vector * Matrix , y la definición debe sea ​​"lanzar un error a menos que las formas sean (N) x (1, M)".

@jiahao :

Cuando hago álgebra lineal con vectores y matrices, no quiero hacer distinciones entre un escalar, un vector de 1 elemento y una matriz de 1 por 1. Actualmente, varias operaciones producen cualquiera de los tres objetos. Mi sugerencia es, para las operaciones de multiplicación y transposición de matrices, elegir una de las tres como representación preferida.

Con respecto a su primer ejemplo ( v==v'' ), me gustaría evitar construir un vector de 1 elemento v en primer lugar.

Con respecto a su segundo ejemplo, sí, creo que v*w' debe manejarse tal como lo describe. Cuando se trabaja con multiplicación y transposición de matrices, quiero que el vector v y la matriz N-por-1 v[:,[1]] denoten el mismo objeto matemático, incluso si tienen diferentes representaciones internas. Esto requeriría un código especial para manejar N-vector multiplicado por una matriz de 1 por M.

Supongo que todavía no está convencido de que la estabilidad de tipos sea importante.

@briansutton : Creo que le resultaría muy informativo intentar implementar la maquinaria necesaria para que su propuesta se ejecute tan rápido como el sistema de tipos actual permite que se ejecute el código de Julia. Yo, por mi parte, creo que sus objetivos declarados son inalcanzables dados los objetivos de Julian de ofrecer un rendimiento predecible y compatibilidad de diseño de memoria C.

No estoy seguro de entender los argumentos que van y vienen, sin embargo, una cosa me llamó la atención. Tal vez sea una ilusión, pero sería bueno si la computadora pudiera pensar tan rápido como el cerebro humano. Lo que quiero decir es que cuando Brian Sutton habla sobre el programa "elegir una representación preferida", visualizo un programa que puede pensar, al igual que nosotros, cuando hacemos matemáticas. Quizás, no sea factible con la tecnología actual y ralentizará demasiado las cosas. Pero, ¿no sería bueno ...

Soy físico y usuario activo de Julia.

Ya no tengo una fuerte preferencia por mantener o eliminar todas las dimensiones finales de singleton.

Pero aquí me gustaría plantear un tema muy relacionado.

La implementación actual de Julia:

Sea V un tensor 3-dim rank-1 (un vector)
V [1] nos dará un escalador, no un tensor de rango 1 de 1 atenuación

Sea A un tensor de rango 3 de 3x4x5
B = A [1,:,:] nos dará un tensor de rango 3 de 1x4x5.

Los dos comportamientos anteriores no son del todo consistentes.

Prefiero encarecidamente la siguiente función de indexación / deslizamiento:

Sea A un tensor de rango 3 de 3x4x5
B = A [1,:,:] nos dará un tensor de rango 2 de 4x5.
C = A [1: 1,:,:] nos dará un tensor de rango 2 de 1x4x5.
(actualmente los dos anteriores dan el mismo resultado)

Sea V un tensor de rango 1
B = V [1] nos dará un escalador
C = V [1: 1] nos dará un tensor 1-dim rank-1.

Esta característica nos ayudará a cambiar la forma del tensor más fácilmente y será útil cuando permitamos índices de singleton finales.

Mejor

Xiao-Gang


De: esd100 [[email protected]]
Enviado: martes, 09 de junio de 2015 9:46 p.m.
Para: JuliaLang / julia
Cc: Xiao-Gang Wen
Asunto: Re: [julia] Tomando en serio las transposiciones vectoriales (# 4774)

No estoy seguro de entender los argumentos que van y vienen, sin embargo, una cosa me llamó la atención. Tal vez sea una ilusión, pero sería bueno si la computadora pudiera pensar tan rápido como el cerebro humano. Lo que quiero decir es que cuando Brian Sutton habla sobre el programa "elegir una representación preferida", visualizo un programa que puede pensar, al igual que nosotros, cuando hacemos matemáticas. Quizás, no sea factible con la tecnología actual y ralentizará demasiado las cosas. Pero, ¿no sería bueno ...

-
Responda a este correo electrónico directamente o véalo en Gi

Quiero decir: C = A [1: 1,:,:] nos dará un tensor de rango 3 de 1x4x5.

Xiao-Gang


De: Xiao-Gang Wen [[email protected]]
Enviado: lunes 22 de junio de 2015 a las 12:01 p.m.
Para: JuliaLang / julia; JuliaLang / julia
Cc: Xiao-Gang Wen
Asunto: RE: [julia] Tomando en serio las transposiciones vectoriales (# 4774)

Soy físico y usuario activo de Julia.

Ya no tengo una fuerte preferencia por mantener o eliminar todas las dimensiones finales de singleton.

Pero aquí me gustaría plantear un tema muy relacionado.

La implementación actual de Julia:

Sea V un tensor 3-dim rank-1 (un vector)
V [1] nos dará un escalador, no un tensor de rango 1 de 1 atenuación

Sea A un tensor de rango 3 de 3x4x5
B = A [1,:,:] nos dará un tensor de rango 3 de 1x4x5.

Los dos comportamientos anteriores no son del todo consistentes.

Prefiero encarecidamente la siguiente función de indexación / deslizamiento:

Sea A un tensor de rango 3 de 3x4x5
B = A [1,:,:] nos dará un tensor de rango 2 de 4x5.
C = A [1: 1,:,:] nos dará un tensor de rango 2 de 1x4x5.
(actualmente los dos anteriores dan el mismo resultado)

Sea V un tensor de rango 1
B = V [1] nos dará un escalador
C = V [1: 1] nos dará un tensor 1-dim rank-1.

Esta característica nos ayudará a cambiar la forma del tensor más fácilmente y será útil cuando permitamos índices de singleton finales.

Mejor

Xiao-Gang


De: esd100 [[email protected]]
Enviado: martes, 09 de junio de 2015 9:46 p.m.
Para: JuliaLang / julia
Cc: Xiao-Gang Wen
Asunto: Re: [julia] Tomando en serio las transposiciones vectoriales (# 4774)

No estoy seguro de entender los argumentos que van y vienen, sin embargo, una cosa me llamó la atención. Tal vez sea una ilusión, pero sería bueno si la computadora pudiera pensar tan rápido como el cerebro humano. Lo que quiero decir es que cuando Brian Sutton habla sobre el programa "elegir una representación preferida", visualizo un programa que puede pensar, al igual que nosotros, cuando hacemos matemáticas. Quizás, no sea factible con la tecnología actual y ralentizará demasiado las cosas. Pero, ¿no sería bueno ...

-
Responda a este correo electrónico directamente o véalo en Gi

Lo que está preguntando es cómo funciona actualmente slice . Mi sensación es que A[stuff] se convertirá en sinónimo de slice(A, stuff) , por lo que probablemente consigas tu deseo.

Estimado Tim:

Gracias por el consejo. He intentado cortar. No se ajusta a mis necesidades. Slice produce un nuevo tipo de datos "subarreglo" que no puedo usar en mi otro código que usa :: tipo de datos Array.

Tal vez pueda cambiar mi otro código para que permitan el tipo de datos "subarreglo".

Xiao-Gang


De: Tim Holy [[email protected]]
Enviado: lunes 22 de junio de 2015 a las 17:32
Para: JuliaLang / julia
Cc: Xiao-Gang Wen
Asunto: Re: [julia] Tomando en serio las transposiciones vectoriales (# 4774)

Lo que está preguntando es cómo funciona el sector actualmente. Mi sensación es que A [cosas] se convertirá en sinónimo de rebanada (A, cosas), por lo que probablemente obtendrás tu deseo.

-
Responda a este correo electrónico directamente o véalo en Gi

¿Hay algo que realmente dependa del uso de los tipos Array concretos en lugar de AbstractArray en su código? Puede que solo requiera una búsqueda / reemplazo de Array por AbstractArray para que todo funcione.

Querido Scott

Muchas gracias por el dato.

Xiao-Gang


De: Scott P. Jones [[email protected]]
Enviado: jueves 25 de junio de 2015 a las 9:55 a.m.
Para: JuliaLang / julia
Cc: Xiao-Gang Wen
Asunto: Re: [julia] Tomando en serio las transposiciones vectoriales (# 4774)

¿Hay algo realmente que dependa del uso de tipos de Array concretos en lugar de AbstractArray en su código? Puede que no requiera nada más que una búsqueda / reemplazo de Array con AbstractArray para que las cosas funcionen.

-
Responda a este correo electrónico directamente o véalo en Gi

¿Eso te sirvió? ¡Estoy feliz de poder ayudar!

@wenxgwen , alternativamente, puede llamar a copy(slice(A,...)) para obtener una copia del segmento en un nuevo Array que luego debería funcionar de forma nativa con sus funciones ya existentes.

Solucionar este problema ahora se ha convertido en un esfuerzo lucrativo

Dado que solucionar este problema ahora es fundamental en mi camino a las 3 comas ...
image

Pero con toda seriedad. Intenté leer la discusión por completo, pero no puedo garantizar que esto no se haya sugerido antes:

¿Podemos posiblemente expandir la definición de AbstractArray para tener un rasgo adicional (similar a LinearIndexing?) Que defina si los datos subyacentes son almacenamiento basado en filas o en columnas? Esta adición abriría las siguientes propiedades (no se centre en los nombres ... solo en los conceptos):

v --> length-2 Vector{Col}
  [ 1
    2 ]

v'  --> length-2 Vector{Row}
  [ 1 2 ]

m --> 2x2 Matrix{Col}
  [ 1 3 
    2 4 ]

m' --> 2x2 Matrix{Row}
  [ 1 2 
    3 4 ]

Some operations:
v'  --> length-2 Vector{Col}
v'' == v
v*v or v'*v'  --> either error, or do element-wise multiplication
v' * v --> scalar
v * v' --> 2x2 Matrix  (could be Row or Col??)
v' * m --> 2-length Vector{Row}
v * m --> either error or broadcasting operation
m * v --> either error or broadcasting operation
m * v' --> 2-length Vector{Col}

Indexing:
v[2] --> 2
v[1,2] --> error
v'[1,2] --> 2
m[1,2]  --> 3
m'[1,2]  --> 2

Size:
length(v)  --> 2
length(v')  --> 2
size(v)  --> (2,)
size(v')  --> (2,)
length(m)  --> 4
size(m)  --> (2,2)

Obviamente, a esta propuesta le faltan muchas definiciones, pero tal vez pueda iniciar la discusión. ¿Podemos admitir el almacenamiento de índices de columnas y de índices de filas al mismo tiempo y "solucionar" algunos problemas en el camino?

Desearía mucho que Julia fuera un almacenamiento basado en filas, ya que así es como pienso naturalmente sobre los bucles y muchas otras operaciones. (y no creo que sea el único que piensa de esta manera) ¡Comentarios por favor!

Es una idea interesante, que el constructor también tome en fila o columna. Creo que se ha discutido antes en algún lugar profundo del sistema de problemas de GitHub. ¡Podría estar equivocado!

Personalmente prefiero el almacenamiento basado en columnas porque la mayoría de mis libros de texto usan columnas para sus matemáticas y luego en Julia no necesito cambiar todo para usar filas. También me pareció extraño al principio, pero rápidamente se convirtió en un problema para mi trabajo. Sin embargo, hay algoritmos que se expresan más fácilmente en filas, por lo que podría ser bueno tener que poder usar un almacenamiento no estándar cuando sea necesario. Espero que la convención sea la de devolver siempre una matriz principal de columna o un vector de columna cuando tiene una función que se exporta de modo que nunca haya una pregunta sobre qué tipo tiene al llamar a una función. De lo contrario, podría complicarse bastante y resultar desagradable de usar cuando tenga que buscar siempre qué tipo se devuelve.

Basado en columnas vs basado en filas no está dentro del alcance de este problema.

@tbreloff Me gusta mucho la idea. Sería genial poder interactuar más fácilmente con lenguajes / bibliotecas que son principales.

Jiahao tiene razón en que preferir la fila principal frente a la columna principal está fuera de tema. Sus
solo un buen efecto secundario que resolver el problema de transposición de una manera "juliana"
(tipos paramétricos y funciones por etapas) da a las personas más flexibilidad en
formato de almacenamiento.

Si no le gusta la idea de agregar un rasgo de fila / columna a las matrices, entonces el
exactamente lo mismo se puede lograr con un TransposeView {T, N}, pero yo
Sospecho que será más complicado de implementar bien.

El mayor inconveniente del rasgo adicional es la confusión para los nuevos usuarios,
y eso es algo que me cuesta conciliar.

El sábado 26 de septiembre de 2015, Scott P. Jones [email protected]
escribió:

@tbreloff https://github.com/tbreloff Me gusta mucho la idea. Eso
sería genial poder interactuar más fácilmente con idiomas / bibliotecas
que son de fila mayor.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -143436947.

¿La sobrecarga de llamadas cambia el espacio de diseño en absoluto? A veces, en lo anterior parecía haber una especie de ambigüedad en el significado de * . En particular, cuando queremos que v::Covector * w::Vector devuelva un escalar, ¿estamos tomando * como "mapa w bajo v " en lugar de una multiplicación de matrices? Si es así, ¿no podría uno exigir de manera similar que w::Vector * v::Covector devuelva un escalar, ya que los vectores son mapas lineales sobre covectores?

Quizás sería útil sobrecargar call y escribir v(w) para indicar la operación "map under v " en w , y de manera similar para w(v) : ambos devolverían escalares. ¿Permitiría esto un mayor margen de maniobra en la adaptación de la semántica que comparten las operaciones de matriz y las operaciones algebraicas lineales en el caso de las matrices 2d?

Si es así, ¿no podría uno exigir de manera similar que w :: Vector * v :: Covector devuelva un escalar, ya que los vectores son mapas lineales sobre covectors?

Creo que estamos tratando de seguir las convenciones de matrices y vectores que mucha gente ve en la universidad de primer año, que no es conmutativa y tiene vector * vector transpuesto -> matriz (de rango 1, es decir, que tiene solo uno (distinto de cero) singular valor). Lo que escribiste suena más como un concepto abstracto de álgebra lineal, donde el vector y los vectores co / duales son mapas entre sí a algún escalar, lo cual está bien pero un poco diferente a lo que (en mi humilde opinión) se intenta en Julia y MATLAB. Creo que podríamos interpretar lo que escribió como el producto interno, dot() o actuando sobre dos covectors (que probablemente _deberíamos_ definir para covectors, creo), pero a veces también queremos el externo -producto, y actualmente el * no acumulativo nos permite escribir y expresar ambos comportamientos.

En cuanto a la convención de llamadas v(w) , también podríamos decir para el producto escalar que queremos la cantidad de v en la dirección de w que sugiere que usemos el operador de indexación v[w] . (Por cierto, no estoy diciendo que esta sea una buena opción de diseño de lenguaje, ¡solo una observación!)

igualmente podríamos decir para el producto escalar que queremos la cantidad de v en la dirección de w que sugiere que usemos el operador de indexación v[w] .

Esta sugerencia casi, pero no se ajusta, a la semántica de la indexación. También choca con nuestro soporte actual para la indexación de vectores si v es un Vector{<:Integer} .

Considere que para v :: Vector{T<:Real} , v[1] es equivalente al producto escalar v⋅e₁ , donde e₁ es el vector de base canónica a lo largo del primer eje. Por lo tanto, la indexación simple es realmente la función

   v[n] : n :: Integer --> y = (v ⋅ eₙ) :: T

Para la indexación de vectores, v[[1, 2]] produce [v⋅e₁, v⋅e₂] que es el resultado de proyectar v en el subespacio generado por {e₁, e₂} , o equivalentemente es el resultado de v' * [e₁ e₂] .

Entonces, la indexación vectorial es la función

   v[I] : I :: Vector{<:Integer} --> y = v' * [eₙ for n in I] :: Vector{T}

La propuesta de hacer v[w] = (w ⋅ v) v es inconsistente con esta definición porque elimina el mapeo implícito de una colección de índices n (como se especifica en w ) a una colección de vectores de base canónica eₙ , que es necesario para que funcionen nuestras reglas de indexación actuales.

Limitando el alcance a solo la transposición vectorial por ahora, creo que tenemos dos opciones. Podemos convertirlo en un error o podemos introducir el tipo de covector especial. Dada la naturaleza de primera clase del vector en Julia, creo que lo primero sería muy difícil de vender ... y para hacerlo de manera consistente probablemente deberíamos prohibir completamente que los vectores participen en el álgebra de matrices.

Así que le disparé al Covector. Es mucho trabajo y, lamentablemente, no podré dedicarle más tiempo. Lo publico aquí con la esperanza de que alguien corra con él o que aprendamos de las dificultades y decidamos huir. Pero tengo un edificio de sucursales con transposiciones como vistas y multiplicación de matrices implementada con covectors. Algunas pruebas seleccionadas pasan, pero solo sin depwarn=error (por ejemplo, ./julia -e 'using Base.Test; include("test/matmul.jl")' ). https://github.com/JuliaLang/julia/compare/mb/transpose

Definí dos nuevos tipos de vista que se usarán para transpose y ctranspose de matrices y vectores:

immutable MatrixTranspose{C,T,A} <: AbstractArray{T,2}
    data::A # A <: AbstractMatrix{T}
end
immutable Covector{C,T,V} 
    data::V # V <: AbstractVector{T}
end

El parámetro C es un booleano simple para representar si la transposición es una transposición o no. Tenga en cuenta que el Covector _no_ es un subtipo de AbstractArray ; este es un requisito bastante fuerte para que el envío funcione de manera razonable.

Algunas desventajas:

  • Los convenios son decididamente complicados y será un desafío documentarlos de manera accesible y rigurosa. El idioma puede ayudar aquí, simplemente podríamos llamarlos RowVector s. Independientemente, la primera dificultad que encuentras al tratar de explicar este comportamiento es cómo hablas de la forma de un covector ( size no está definido). Si (m,n) es la forma de una matriz, entonces (m,) puede representar la forma de un vector ... y si abusamos de la notación de tupla, podemos describir argotosamente un covector para que tenga la forma (,n) . Esto nos permite expresar reglas mixtas de álgebra de vectores / matrices con una dimensión "faltante" que se combina y se propaga de forma algo sensata:

    • Matriz * El vector es (m,n) × (n,) → (m,)

    • Covector * La matriz es (,m) × (m,n) → (,n)

    • Vector * Covector es (n,) × (,n) → (n,n)

    • Covector * El vector es (,n) × (n,) → α (escalar)

  • El número de operaciones y tipos binarios aquí conduce a una enorme explosión combinatoria en el número de métodos que deben definirse ... solo para la multiplicación tenemos:

    • Mutación: (mutante, no mutante)
    • Transponer: (A, Aᵀ, Aᴴ) × (B, Bᵀ, Bᴴ).
    • Forma: (Mat × Mat; Vec × Mat; Mat × Vec, Vec × Vec). Tenga en cuenta que no todos estos son compatibles con todas las combinaciones de transposiciones, pero la mayoría sí lo son.
    • Implementación: (BLAS, Strided, genérico, más especializaciones para matrices estructurales)

    Si bien algunas de estas operaciones se alinean muy bien con múltiples comportamientos de envío y reserva, sigue siendo un número _ enorme_ de métodos para cada operador. Eliminar completamente el soporte de matriz / vector mixto aquí definitivamente ayudaría a simplificar las cosas.


  • No resuelven de inmediato ninguna de las dificultades de eliminar todas las dimensiones escalares. Apoyar cualquier tipo de transposición vectorial cuando dejamos caer dimensiones escalares nos pone en territorio ambiguo con respecto al conjugado complejo (ver https://github.com/JuliaLang/julia/pull/13612). Quizás podríamos hacer que A[1,:] devuelva un covector, pero no se generaliza bien y sería bastante extraño devolver un no AbstractArray de un segmento. Sería genial si la gente pudiera probar el número 13612 y buscar específicamente casos en los que la transposición conjugada de un vector cause problemas; sería igualmente malo con la semántica de transposición de vectores actual y no requiere que los Covectors expongan.

Algunas ventajas:

  • Usar el envío directamente en lugar del análisis especial de Ax_mul_Bx es una gran ganancia. Se compone muy bien con la API de BLAS. En general, desea hacer la conjugación en los niveles más internos de los algoritmos, por lo que tiene mucho más sentido mantener esta información con los argumentos. Para las llamadas BLAS, se puede usar una función simple para buscar el carácter de transposición ( ntc ).
  • La multiplicación entre vectores y covectores ahora es asociativa porque v'v devuelve un escalar. Ahora puede evaluar v'v*v de izquierda a derecha y evitar formar la matriz.
  • Esto permite la eliminación de Vector * Matrix, que solo funciona si trata todos los vectores como si tuvieran dimensiones finales de singleton ... y es un gran paso para eliminar por completo el soporte para las dimensiones de singleton finales en general.

Otras notas:

  • Tener la complejidad de la transposición representada por un parámetro de tipo booleano funciona bien, pero a menudo sentí que había definido los parámetros en el orden incorrecto. Rara vez quería restringir o capturar tanto T como C . No estoy seguro de si hacerlo de la otra manera sería mejor en términos de la cantidad de typevars de marcador de posición que necesitaría definir, pero al menos coincidiría con AbstractArray{T} .
  • Esto realmente exacerba las dificultades de StridedArray. Leer la tabla de métodos para * es todo un desafío con tipos como ::Union{DenseArray{T<:Union{Complex{Float32},Complex{Float64},Float32,Float64},2},MatrixTranspose{C,T<:Union{Complex{Float32},Complex{Float64},Float32,Float64},A<:Union{DenseArray{T,1},DenseArray{T,2},SubArray{T,1,A<:DenseArray{T,N},I<:Tuple{Vararg{Union{Colon,Int64,Range{Int64}}}},LD},SubArray{T,2,A<:DenseArray{T,N},I<:Tuple{Vararg{Union{Colon,Int64,Range{Int64}}}},LD}}},SubArray{T<:Union{Complex{Float32},Complex{Float64},Float32,Float64},2,A<:DenseArray{T,N},I<:Tuple{Vararg{Union{Colon,Int64,Range{Int64}}}},LD}} . Claro, lo he escrito como typealias, pero incluso administrar todos estos nombres de alias de tipos diferentes es un problema ( StridedMatOrTrans , QRCompactWYQorTranspose , etc.).
  • No he tenido la oportunidad de integrar esto con SparseVector, pero será una gran victoria ya que representará la transposición de manera eficiente sin necesidad de un CSR.
  • ¿Necesitamos definir indexación escalar y / o no escalar en Covectors? Si es así, ¿cuál es el resultado de r[:] o r[1:end] ? ¿Es un vector o un covector? Creo que trataría de escapar sin definir esto si podemos. Curiosamente, Matlab tiene reglas muy especiales para indexar vectores de fila con otros vectores: se esfuerzan mucho por mantener su fila a costa de algunos casos de esquina extraños ( r((1:end)') es r(1:end) es r(:)' ). Tengo la indexación escalar definida en mi rama por ahora, pero quizás eso también debería eliminarse. Eso dejaría en claro que el Covector solo se debe usar con operaciones de álgebra lineal que lo conocen.

Finalmente, solo quiero señalar que la mayoría de las ventajas que veo aquí son igualmente aplicables al tipo MatrixTranspose por sí mismo. Creo que esto demuestra que el Covector puede funcionar bastante bien sin profundizar demasiado en las álgebras abstractas para permitir operaciones mixtas de Vector / Matrix. Definitivamente agrega una buena característica (la capacidad de usar vectores de manera consistente con álgebra lineal), pero no estoy seguro de que valga la pena la complejidad adicional.

Por curiosidad, si todavía recuerdas a @mbauman , ¿qué te motivó a introducir el parámetro booleano C ? ¿No puedes simplemente tener (en pseudotraits)

transpose(::Matrix) -> MatrixTranspose
transpose(::MatrixTranspose) -> Matrix

? No estoy dudando de su juicio (excepcional) aquí, solo quiero comprender qué descubrimientos lo obligaron a hacer esto.

Presumiblemente, se podría generalizar esto a un tipo PermutedDimensionArray , con un parámetro de tupla que codifica la permutación. El tipo Covector obviamente tiene un propósito diferente y debe ser una cosa separada.

Necesita alguna forma de manejar las transposiciones conjugadas (y también matrices conjugadas no transpuestas por (A').' ). Veo tres formas obvias de hacer esto:

  • Almacene la conjugación como un campo booleano dentro de un solo tipo MatrixTranspose . Tras reflexionar, creo que esta es definitivamente la mejor opción para interactuar con BLAS externo. Sin embargo, en términos de JuliaBLAS nativa, me gustaría asegurarme de que Julia / LLVM pueda sacar T.isconjugate ? conj(T[i,j]) : T[i,j] de los bucles.
  • Un tipo con un parámetro de conjugación. Esto es lo que elegí, y creo que me influyó el hecho de que actualmente hacemos pseudo-despacho en la conjugación a través de Ac_mul_Bt y amigos. También tiene garantías más sólidas sobre las optimizaciones de eliminación de ramas que Julia puede hacer. Pero no lo pensé mucho… solo quería comenzar con la implementación de un boceto, y estaba más preocupado por el Covector.
  • Dos tipos separados, MatrixTranspose y MatrixCTranspose . Isomorfo a un parámetro de tipo, pero encuentro dos tipos de envoltorios separados molestos. Los supertipos abstractos y los alias de unión pueden ayudar, pero aún así elegiría el parámetro sobre esta opción.

Supongo que ahora hay una cuarta opción con funciones escritas, almacenando cualquier función de transformación dentro del tipo de transposición ... pero eso también requiere un parámetro de tipo para que la función sea rápida, y luego es un parámetro de tipo que tampoco se envía fácilmente.

Me pregunto si podríamos tener un contenedor ConjugateView , con la regla conj y transpose asegurarnos de que ConjugateView se coloque dentro del contenedor MatrixTranspose . Es decir, por A a Matrix ,
A' = conj(transpose(A)) = transpose(conj(A)) todos producen un MatrixTranspose{ConjugateView{Matrix}} (eliminando los parámetros de tipo no informativo).

Ah, sí, eso también es sensato. Tiendo a pensar en la transposición conjugada como una "cosa" atómica, así que me perdí esa opción. Estaba pensando en cómo las transposiciones no conjugadas podrían representarse mediante un tipo de índice de subarreglo especial, al igual que las remodelaciones.

¡Me alegro de que todavía estén trabajando en esto! ¡Este es un hilo épico! ¡¡¡Tres hurras!!!

¿Está previsto esto para la 1.0?

En dos números (# 18056, # 18136) me señalaron este hilo gigante.
Así que lo intento.

Ahora Julia tiene verdaderos vectores unidimensionales, _e.g._ mx[row,:] ya no es una matriz 1xn.
¡Este es un cambio bienvenido!

Pero como efecto secundario, algunos problemas existentes se hicieron más obvios.
Me mordió el hecho de que v*mx no funciona para vectores unidimensionales.
Matemáticamente debería funcionar naturalmente y devolver un vector 1-d,
cuando el producto a*b se define mediante la contratación
el último índice del primer término y el primer índice del segundo término.

En la actualidad, la firma del método del producto Vector-Matrix es:
(*)(A::AbstractVector, B::AbstractMatrix) = reshape(A,length(A),1)*B
y este método se usa para el caso v*v' y no para v*mx .
(Gracias @andreasnoack por señalar esto).
Obviamente, no se puede utilizar un solo método para ambos.

Parece que Julia está luchando con algunas convenciones similares a Matlab.
Pero en Matlab no hay vectores 1-d, solo matrices 1xn y nx1,
tantas cosas que son naturales que puede haber un problema aquí.
Julia tiene verdaderos vectores 1-d que deberían ser una gran ventaja.
Sería bueno alcanzar un estado realmente consistente por sí solo.

Fortran es un ejemplo mucho mejor y más consistente a seguir en este caso.
La operación transpose está definida solo para matrices en Fortran,
crear una matriz 1xn a partir de un verdadero vector 1-d simplemente no es una transposición.
Por matmul vea el extracto del libro de Metcalf citado en # 18056.

Creo que la mayoría de los puntos originales de @alanedelman eran correctos.

Así que aquí hay una sugerencia que simplemente solucionaría algunos problemas existentes,
respetando al máximo el estado actual:

  • mantenga v' como está para crear una matriz 1xn a partir de un vector 1-d verdadero v
  • rowmx funciones colmx serían mejores, pero v' está demasiado extendido para cambiarlo
  • ya tenemos la función vec para crear un verdadero vector 1-d
  • aunque la inversa de v' no es v'' sino vec(v') , podemos vivir con ello
  • el producto a*b siempre debe contraer el último índice de a y el primer índice de b
  • el operador * no debe usarse ni para productos internos ni externos
  • para productos internos ya tenemos la función dot
  • para productos externos, el uso de * debe eliminarse (_ es decir, la sintaxis v*v' )
  • para productos externos se debe utilizar una nueva función
  • un operador infijo correspondiente podría mantener la sintaxis ligera

Perder [sintaxis matemática concisa para] productos externos sería lamentable. Prefiero renunciar personalmente a vec * mat.

¿Puede el PernutedDimsArray existente ya manejar el caso donde padre y vista tienen diferentes números de dimensiones? Si es así, tal vez ya sea utilizable como el tipo de envoltura de transposición no conjugada incluso para padres vectoriales.

No encontré PermutedDimsArray en la documentación.

Pero creo que, en lugar de inventar más tipos de datos,
solo los tres tipos de productos de matriz deben separarse claramente.
Los productos internos ya están separados.
Solo necesitamos separar los productos normales y externos.
Los productos externos no se perderían, solo cambiaría su sintaxis.
Considere el caso v*mx solo como un síntoma del problema más profundo.

Aparte del "problema de métodos faltantes", no sería un tipo del que tendría que preocuparse, sería un detalle de implementación que .' devuelve un tipo de envoltorio perezoso (y ' una versión conjugada del mismo). De lo contrario, no creo que podamos tener tanto vec*mat como vec*vec' usando el mismo operador * . Si vi vec*mat en un papel, me parecería mal, pero veo vec*vec' bastante frecuencia.

Sin embargo, pensando más en ello, creo que PermutedDimsArray no transpone de forma recursiva sus elementos para matrices de matrices, por lo que no es del todo adecuado como tipo de contenedor para usar aquí.

La otra alternativa es no permitir la transposición de vectores por completo. Creo que la discusión aquí ya ha sido demasiado exhaustiva, y solo estamos esperando una implementación integral de una o ambas opciones para evaluar cómo se verá el impacto.

Aprecio que estuvieras listo para una discusión mientras este hilo está cerca de su final.

Si bien v*mx puede parecerle extraño, se usa mucho en código cristalográfico.
También está bien manejado por matmul de Fortran. (Ver # 18056)

Volver al producto u*v' .
Cuando u y v son matrices nx1, este es un producto matricial normal,
que resulta proporcionar el mismo resultado que el producto exterior.
Pero esto es solo usar el producto de matriz para emular el producto externo.
Esto es perfecto en el mundo de Matlab, donde todo es una matriz.

En Julia tenemos verdaderos vectores 1-d que están más cerca del mundo de Fortran.
En Julia, el vector transpuesto v' ya es un problema,
La elección de Fortran es prohibirlo, como ya le sugirieron otros a Julia.

Fortran no tiene una función intrínseca para los productos externos.
Julia transforma fácilmente v' en una matriz 1xn,
y realiza la operación * en un vector 1-d y una matriz 1xn.
Debido al envío múltiple, puede hacer esto,
pero aquí * seguramente ya no es un producto de matriz.

El punto donde Fortran y Julia se comportan de manera similar
es que ambos usan una función dot separada para productos internos.
Hubo una discusión para agrupar dot también en el operador * ,
pero afortunadamente no sucedió.

Entonces tenemos que manejar los tres productos del álgebra lineal:
producto de matriz normal, producto interior y producto exterior.
En la actualidad, el operador * es una sintaxis combinada para productos normales y externos.
Todo lo que he sugerido es separarlos y alcanzar un estado más consistente.

(Oh, dejé fuera el producto cruzado, pero ya está bien separado)

No creo que el producto interno, los productos externos y la multiplicación de matrices sean una forma matemáticamente sólida de dividir / clasificar productos. Lo siguiente es ciertamente una repetición de cosas que varias personas han dicho anteriormente, pero dada la extensión de este tema, espero que esté bien incluir un resumen de vez en cuando. Este es mi resumen personal y ciertamente no soy un experto, así que corrígeme donde me equivoque.

En un entorno de álgebra lineal abstracta, los jugadores principales son los vectores v (que viven en un espacio vectorial V ), mapas lineales (que actúan en un espacio vectorial V y mapean a un espacio posiblemente diferente W ) y composición de estos mapas, formas lineales o covectors (viviendo en un espacio dual V* y mapeo desde V a escalares), productos internos (desde V × V a escalares), productos tensores entre vectores (es decir, kron ). Producto externo que prefiero considerar como un producto tensorial entre un vector y un covector.

De particular interés para este tema es el isomorfismo entre vectores y formas lineales si se define un producto interno, es decir, por cada f vectores de mapeo v a escalares, existe un w tal que f(v) = dot(w,v) para cualquier v . Sin embargo, los coadyuvantes pueden existir sin referencia a productos internos (por ejemplo, el gradiente de una función multidimensional).

Para representar todos esos objetos en una computadora, normalmente eliges una base y luego puedes representar la mayoría de estas cosas usando álgebra matricial. Es decir, solo necesita la multiplicación y transposición de matrices si está dispuesto a ser flexible con las dimensiones finales de singleton (vectores como matrices nx1, escalares como matrices 1x1, etc.). Esta ha sido la opinión de Matlab, así como la forma en que se escriben muchos libros y artículos, especialmente en álgebra lineal numérica.

En ese caso, el isomorfismo antes mencionado de vectores a covectores (asumiendo implícitamente el producto interno euclidiano) corresponde a tomar la transposición (conjugado hermitiano en caso complejo) de la representación matricial de un vector. Sin embargo, en el sentido abstracto, no existe la noción de la transposición de un vector, por lo que probablemente no está definido en Fortran, como lo afirma @GaborOszlanyi . Solo se define la transposición de un mapa lineal (esto no requiere un producto interno y su representación matricial corresponde a la matriz transpuesta incluso en el caso complejo), así como el adjunto de un mapa lineal (esto sí requiere un producto interno) .

Debido a la importancia de la estabilidad de tipos en el código de Julia, el enfoque de "matrices únicas" de Matlab (con dimensiones simples finales flexibles) no funciona bien. Pero tampoco queremos hacer la transición a un entorno totalmente abstracto y seguir trabajando en el entorno típico (productos internos euclidianos, mapeo trivial de vectores a covectores, ...). Ya que al final estamos escribiendo código y queremos usar caracteres ASCII, necesitamos exprimir tanto como sea posible los símbolos * , ' y .' . Afortunadamente, aquí es donde el envío múltiple es útil y conduce a las diversas propuestas mencionadas anteriormente. Hice una tabla sobre esto, es decir, cómo las operaciones abstractas de álgebra lineal se traducirían en métodos julia específicos (no funciones).

Como nota final para @GaborOszlanyi , todavía no encuentro lugar para v*A en todo esto. Esto podría ser estándar en campos donde los vectores se denotan por defecto como matrices de filas, pero personalmente creo que esta es una elección extraña. Si los mapas lineales f y g actúan como f(v) = v*A y g(v) = v*B , entonces esto significa que g(f(v)) = (g ◦ f)(v) = v*A*B que es impar desde el orden de composición se intercambia. Si hay alguno, podría interpretar esto como un producto interno incompleto, como en la penúltima fila de la tabla vinculada.

Tu resumen es profundo y convincente.
Gracias por explicarlo tan bien.

Solo tengo dos preguntas restantes:

  • ¿Qué cambios ocurrirán realmente en Julia como resultado de esta discusión exhaustiva?
  • ¿Por qué Fortran implementó v*mx en matmul ?

Hay dos problemas expuestos por este problema:

A. Los matemáticos aplicados están casados ​​con la notación Householder, que implícitamente hace uso de dos isomorfismos naturales:

  1. Un vector de longitud N es indistinguible de una matriz de columna Nx1, ya que las matrices Nx1 forman un espacio vectorial. ("columnificar", ▯)
  2. Una matriz 1x1 es indistinguible de un número escalar, ya que las matrices 1x1 pueden definirse con todas las propiedades algebraicas de los escalares. ("escalarizar", ■)

El problema es que ninguno de estos isomorfismos es natural de expresar en los tipos de Julia y ambos implican verificaciones en tiempo de ejecución de la forma de la matriz. Las reglas de dimensión de singleton final de MATLAB se pueden considerar como una implementación de ambos isomorfismos.

B. Una matriz bidimensional se puede definir de forma recursiva como una matriz de matrices. Sin embargo, una matriz es una fila de columnas o una columna de filas, pero nunca una fila de filas o una columna de columnas. El hecho de que las matrices no se puedan definir de forma recursiva resalta la estructura tensorial diferente de las matrices y las matrices de n dimensiones. Hablando con propiedad, los arreglos multidimensionales son un tipo de tensor muy limitado y carecen de la maquinaria completa necesaria para implementar este último. Debido a que una contracción estrictamente hablando es una operación sobre un emparejamiento de un espacio vectorial con su dual, es un nombre inapropiado hablar de contraer los índices de matrices multidimensionales, que nunca invocan el concepto de espacio vectorial dual. La mayoría de la gente _no_ quiere la maquinaria completa, lo que requiere preocuparse por índices co- / contravariantes o arriba / abajo con naturaleza de fila / columna. En cambio, la mayoría de la gente quiere que las matrices sean simples contenedores viejos, totalmente contravariantes en todas las dimensiones, excepto en dos dimensiones, por lo que la mayoría de los usuarios quieren pensar que las matrices bidimensionales tienen el álgebra de matrices (que son tensores de abajo hacia arriba), y nunca los tensores abajo-abajo, arriba-arriba o arriba-abajo. En otras palabras, las matrices bidimensionales quieren tener una carcasa especial.


Todavía puedo persuadirme de lo contrario, pero aquí está mi propuesta actual de 3 partes:

a) No permitir la transposición de vectores por completo, requiriendo que los usuarios conviertan explícitamente vectores en matrices de columnas para escribir expresiones de estilo Householder como u'v , u*v' , u'*A*v y u'*A*v/u'v . Todas estas expresiones se pueden construir a partir de solo tres operadores unarios y binarios elementales: matmul, transposición de matriz y división de matriz. Por el contrario, si u y v son vectores verdaderos, entonces no es posible dar subexpresiones como u' o u'*A sin introducir un TransposedVector especial

Una limitación de (a) sería que todas las expresiones de estilo Householder generarán matrices 1x1 en lugar de escalares verdaderos (lo que sigue siendo una gran mejora con respecto a u'v devuelve un 1-vector), por lo que una expresión como (u'*v)*w todavía no funcionaría. En la práctica, no creo que las expresiones de "producto triple" como esta ocurran con frecuencia.

b) Introduzca una notación alternativa para las operaciones análogas en vectores, como

  • u ⋅ v = scalarize(columnify(u)'*columnify(v)) para el producto interno (punto)
  • u ⊗ v = columnify(u)*columnify(v)' para el producto externo (Kronecker)
  • A(u, v) = scalarize(columnify(u)'*A*columnify(v)) , una notación antigua para la forma bilineal
  • A(u) = A(u, u) para la forma cuadrática

Las expresiones en (b) difieren de sus contrapartes en (a) al escalarizar automáticamente las expresiones para el producto interno y las formas bilineales / cuadráticas, evitando así la formación de matrices 1x1.

c) Haga que las matrices 1x1 sean convertibles a verdaderos escalares, de modo que el código como

M = Array(Int, 1, 1, 1)
a::Int = M

podría funcionar. Todo lo que se necesitaría es hacer la verificación del tiempo de ejecución para el tamaño de la matriz con algo como:

function convert{T}(::Type{T}, A::Array{T,N})
    if length(A) == 1
        return A[1]
    else
        error()
    end
end

Esta propuesta es una pequeña modificación de lo que propuso Folkmar Bornemann hace unos dos años. Cuando lo probamos, el costo de la verificación del tiempo de ejecución no fue muy alto, y solo se invocaría en una asignación coaccionada por tipo (que en realidad es una llamada convert disfrazada), no una asignación general.

@jiahao , la legibilidad de esta publicación está limitada por los caracteres difíciles de representar. Ni siquiera se ve bien en OS X, que generalmente tiene bastante Unicode en sus fuentes.

@jiahao , ciertamente podría estar de acuerdo con la mayoría / todo eso, aunque me gustaría cuestionar dos puntos:

sin introducir un tipo especial TransposedVector , que como consecuencia lógica requiere que las matrices multidimensionales se preocupen por los índices hacia arriba / hacia abajo en su estructura tensorial, lo que parece un precio demasiado alto a pagar.

Esto solo me parece una consecuencia lógica si desea que TransposedVector un subtipo de la jerarquía AbstractArray , que asumí que no era el plan (a diferencia de algunos LazyTranspose tipo que realmente es solo otro tipo de AbstractMatrix ).

u ⊗ v para el producto externo (Kronecker)

Si el objetivo es no depender de isomorfismos implícitos y separar limpiamente las operaciones matriciales de las operaciones vectoriales y productos más abstractos, creo que esto falla por la siguiente razón (no estoy seguro del acuerdo universal sobre las siguientes definiciones matemáticas):

  • El producto de Kronecker A ⊗ B es una operación definida en matrices, no en vectores.
  • Por lo tanto, en lugar de leer u ⊗ v como el producto tensorial de dos vectores, esto daría lugar a un tensor bidimensional del tipo down down. La única forma de obtener una 'matriz' adecuada sería tomar el producto tensorial de un vector con un covector. Pero como se quiere evitar introducir estos objetos, parece que es imposible obtener el resultado de esta operación antes de columnificar los dos vectores involucrados.
  • Un tercer nombre para u ⊗ v es el producto externo que generalmente se define de manera descuidada, y no estoy seguro de si existe una definición rigurosa aceptada en términos de arriba y abajo. Algunas fuentes afirman que es equivalente al producto tensorial de dos vectores, de ahí el punto anterior. Si, en cambio, acepta una definición donde 'producto externo' también significa mapear implícitamente el segundo vector a un covector para obtener un tensor hacia abajo, entonces no hay problema.

En cambio, la mayoría de la gente quiere que las matrices sean simples contenedores viejos, totalmente contravariantes en todas las dimensiones, excepto en dos dimensiones,

¿Hemos considerado la opción nuclear? Comenzamos el álgebra lineal de nuevo, eliminando por completo los alias de tipo para AbstractVector y AbstractMatrix y reemplazándolos con:

abstract AbstractVector{T} <: AbstractArray{T,1}
abstract AbstractMatrix{T} <: AbstractArray{T,2}

# and we could introduce:
abstract AbstractCoVector{T} <: AbstractArray{T,1}

Entiendo que hay muchas consecuencias, pero podríamos terminar con una separación limpia entre matrices de almacenamiento multidimensionales y álgebra lineal. No tenemos que implementar el álgebra tensorial multidimensional completa, los espacios vectoriales duales, etc. Solo tenemos que implementar los bits que mucha gente quiere: vectores, covectores y matrices. Obtenemos productos internos, productos externos, productos de matriz de cobertura, productos de matriz-vector y productos de matriz-matriz. La transposición se define en todo lo anterior. Podemos tener implementaciones de AbstractMatrix que realmente usan una matriz 1D de matrices 1D (no la implementación predeterminada, por supuesto). No tenemos que usar la notación Householder (que en mi humilde opinión es una de las debilidades de MATLAB), pero aún podemos obtener todas las comodidades del álgebra lineal.

Estoy un poco nervioso por sugerir esto, pero creo que permitirá a Julia imitar el modelo "correcto" de lo que la mayoría de la gente aprende, por ejemplo, en álgebra lineal de primer año universitario, sin necesidad de recurrir a las convenciones de los jefes de hogar. Hacer una distinción clara también puede facilitar la transferencia de Base.LinAlg a un paquete de "biblioteca estándar", que creo que es un objetivo a largo plazo para Julia. También encaja bien con la idea de que habrá una nueva lista 1D redimensionable que vendrá con los nuevos cambios Buffer y la implementación nativa de Array , por lo que este tipo genérico de "lista" puede reemplazar Vector para muchas de las partes centrales de Julia y dejarnos cargar el paquete LinAlg bastante tarde mientras tenemos Array y "list" definidos bastante temprano.

Existen muchos algoritmos que ya han sido "simplificados" y que se expresan en términos de matrices de Fortran y no en términos de álgebra lineal adecuada. Julia necesita poder implementar estos algoritmos sin que las personas tengan que redescubrir (o coaccionar) una estructura de álgebra lineal sobre matrices multidimensionales.

En mi línea de trabajo, probablemente lo mejor sea un álgebra lineal adecuada que mapee tensores y índices co / contravariantes, etc. en matrices de Fortran. Para que esto funcione, y para no confundir a la gente, dejaría los términos "vector", "matriz" y "matriz", manteniéndolos en el nivel bajo, y usaría otros términos (¿más elegantes?) Para todo el nivel superior. . Dicho nivel superior también debería abstraer las opciones de almacenamiento, ya sea mediante tipos abstractos o mediante parametrización. Quizás un prefijo LA es el camino a seguir para expresar esto:

LA.Vector
LA.CoVector
LA.Tensor{... describing co/contravariance of indices ...}

Entonces, los vectores y las matrices se utilizan exclusivamente para el almacenamiento. En el nivel bajo, la transposición se gestiona manualmente (igual que en BLAS); en el nivel alto, se maneja automáticamente.

Esto está cerca de la sugerencia de @andyferris , excepto que no rompe la compatibilidad con versiones anteriores y no rompe las expectativas de Matlab / Fortran / numpy convert.

@eschnett Creo que anteriormente en este hilo se decidió que el álgebra multilineal se dejaría para los paquetes, en lugar de la base Julia. Creo que muchas de estas ideas, como usted sugiere, podrían desarrollarse en un nuevo paquete que trate tensores, espacios vectoriales, co / contra-varianza, etc., dentro del sistema de tipos, algo diferente al paquete _TensorOperations.jl_ que proporciona funciones de conveniencia. para multiplicar matrices como si fueran tensores. Creo que sería un desafío desarrollarlo, ¡pero es una abstracción potencialmente valiosa!

En cuanto a dejar Matrix y Vector solos, bueno, quizás las definiciones actuales sean perfectas, o quizás haya algo mejor que podamos hacer por la gente que quiere multiplicar matrices y transponer vectores, usando matemáticas estándar notación. Supongo que puede agregar una pequeña curva de aprendizaje a los nuevos usuarios, aunque esperaba que si el sistema era obvio y elocuente y una mejora en otros idiomas, entonces debería ser fácil de aprender.

Tenga en cuenta que un espacio vectorial complejo no euclidiano general V tiene 4 espacios asociados: V , conj(V) , dual(V) y conj(dual(V)) . Entonces, un tensor general que tiene 4 tipos de índices (generalmente denotados como arriba o abajo, con barras o sin barras). En un espacio euclidiano complejo (por ejemplo, mecánica cuántica), dual(V) ≡ conj(V) y conj(dual(V)) = V . En un espacio real (no euclidiano) (por ejemplo, relatividad general), V ≡ conj(V) . En los dos últimos casos, solo se necesitan índices hacia arriba y hacia abajo.

En un espacio euclidiano real (¿la mayoría de las aplicaciones?), Todas son equivalentes, y las matrices simples de julia son suficientes para representar tensores. Entonces, al menos para números reales, las siguientes dos operaciones permiten construir un álgebra tensorial completa y consistente.

  • contrato / producto interno , que podría generalizarse a matrices arbitrarias de rango N usando la regla: contrate el último índice de la primera matriz con el primer índice de la segunda (o dot numpy A ∙ B devuelve una matriz de rango M+N-2 si A y B tenían rango M y rango N .
  • producto tensorial : A ⊗ B devuelve una matriz de rango N+M

Especializado hasta los vectores v , w y matrices A , B , esto permite escribir todo lo siguiente:

  • producto interno del vector v ∙ w -> excepcionalmente devuelve un escalar en lugar de una matriz de rango 0
  • multiplicación de vectores de matriz A ∙ v
  • matriz multiplicación de matrices A ∙ B
  • tensor / producto externo v ⊗ w
  • covector (=== vector) multiplicación de matrices v ∙ A

Si bien uno podría querer usar * por , la trampa es que la definición anterior de no es asociativa: (A ∙ v) ∙ w ≠ A ∙ (v ∙ w) ya que este último ni siquiera estar definido.

Pero, de nuevo, eso es solo si está dispuesto a ignorar matrices complejas.

En cuanto a una implementación completamente general de tensores, comencé (y abandoné) esto hace mucho tiempo, antes de que hubiera tuplas asignadas a la pila, funciones generadas y todas estas otras ventajas que probablemente lo harían más factible hoy.

Enfoque interesante, @Jutho. Parece bastante intuitivo. _Supongo_ que estas definiciones podrían agregarse independientemente de lo que hagamos con * y ' , y lo apoyaría.

Pero, de nuevo, eso es solo si está dispuesto a ignorar matrices complejas.

Insertado manualmente conj() corrige eso. Y no creo que Julia quiera ignorar las matrices complejas, ¿verdad?

Noté un par de cosas más acerca de tener AbstractMatrix como un subtipo especial en lugar de un alias de tipo:

matrix[:,i] -> AbstractVector{T}
matrix[i,:] -> AbstractCoVector{T}
array_2d[:,i] -> AbstractArray{T,1}
array_2d[i,:] -> AbstractArray{T,1}

Esto es muy bueno: podemos obtener vectores de columna y fila al indexar una matriz. Si es solo un contenedor de almacenamiento 2D, obtenemos una matriz de almacenamiento 1D. ¡Debería cubrir todos los casos de uso! Y todavía obedece la interfaz AbstractArray con reglas de corte APL desde AbstractVector{T} <: AbstractArray{T,1} y AbstractCoVector{T} <: AbstractArray{T,1} .

Un hecho "interesante" es

array_3d[:,:,i] -> AbstractArray{T,2}
matrix(array_3d[:,:,i]) -> `AbstractMatrix {T}

por lo que tendría que envolverlo manualmente como una matriz si desea hacer una multiplicación de matrices con el resultado. ¿Es esto una ventaja o una desventaja, no lo sé? Pero esto parece ser una complicación potencial y toca lo que @eschnett mencionó sobre facilitar las cosas a los usuarios de otros lenguajes que combinan almacenamiento y álgebra lineal.

Esta puede ser una pregunta tonta, pero @jutho mencionó anteriormente que la escritura de código estaba limitada por ASCII. ¿Por qué diablos seguimos limitándonos a un conjunto de caracteres de 7 bits desarrollado en 1963 y actualizado por última vez en 1986 (hace 30 años)? Esta fue una era en la que el famoso 640KB era la RAM máxima disponible en una PC en 1981. En el mercado de computadoras actual, ahora comúnmente tenemos procesadores de 64 bits con 32GB de RAM a la venta (50,000 veces el máximo anterior) y no estamos ni cerca de el límite teórico para procesadores de 64 bits. Entonces, ¿por qué seguimos limitándonos a un conjunto de caracteres desarrollado hace 40 años?

Podemos usar unicode, solo tenga en cuenta que, desafortunadamente, el tamaño del teclado no creció en un factor similar en los últimos 40 años ni tampoco la cantidad de dedos que tiene una persona normal (en los últimos 10000 años después).

En mi humilde opinión, ASCII debería dejarse de lado. Un teclado matemático especial para la programación rápida de símbolos matemáticos es realmente una buena idea. ¿Existe una buena razón para no utilizar más símbolos que vienen con UTF? ¿Puede justificar los dolores de cabeza de intentar extraer todo lo que pueda de ASCII?

@ esd100 : No use este problema de GitHub para discusiones tan especulativas.

@Johnmyleswhite. No estoy seguro de lo que quiere decir con "especulativo". ¿Estás seguro de que esa es la palabra que querías usar? Pensé que el uso de ASCII frente a otra cosa era relevante para la discusión, ya que parece que el lenguaje, los operadores y la funcionalidad están ligados a los símbolos utilizados. No soy un experto de ninguna manera, pero parece un problema al discutir, o al menos tener claro, en relación con la funcionalidad que se está abordando.

Quiero decir, ¿hay alguna razón por la que no podamos definir el lenguaje de la manera que lo queremos (con los objetivos fundamentales en mente: facilidad de uso, velocidad)? ¿El uso de símbolos / palabras especiales no haría el lenguaje más rico y hermoso?

@ esd100 tenga en cuenta que, por ejemplo, el comentario más reciente de @Jutho (usando 2 símbolos Unicode) fue bien recibido y @yuyichao está de acuerdo en que podemos usar Unicode. Hay otras propuestas sobre la introducción de más operadores solo unicode (por ejemplo, el símbolo de composición para funciones de composición, # 17184). No creo que la gente esté en desacuerdo con usted (aunque tenemos algunas consideraciones prácticas a tener en cuenta), pero si tiene sugerencias _específicas_ sobre el tema, háganoslo saber.

Estoy un poco confundido por este nuevo comportamiento en v0.5 que creo que surgió de esta discusión:

julia> [ x for x in 1:4 ]' # this is fine
1×4 Array{Int64,2}:
 1  2  3  4

julia> [ Symbol(x) for x in 1:4 ]' # bit this isn't? What is special about symbols?
WARNING: the no-op `transpose` fallback is deprecated, and no more specific
`transpose` method for Symbol exists. Consider `permutedims(x, [2, 1])` or writing
a specific `transpose(x::Symbol)` method if appropriate.

¿Cómo hago un vector de fila (no numérico) a partir de una lista de comprensión? Tiene que ser un vector de fila. (¿Sería mejor esto como un tema separado o discutido en otro lugar? No estaba seguro de dónde publicar ...)

Puede usar remodelar: reshape(v, 1, length(v)) . ¿Quizás esto también debería mencionarse en la advertencia de desactivación? Creo que la idea es que la transposición es una operación matemática y, por lo tanto, solo debería definirse para vectores / matrices matemáticos.

Se menciona en la advertencia: permutedims(x, [2, 1]) .

permutedims no funciona para vectores. Vea este nuevo número: # 18320

Creo que la idea es que la transposición es una operación matemática y, por lo tanto, solo debería definirse para vectores / matrices matemáticos.

Esto no tiene ningún sentido para mí. ¿Está esto en discusión? Realmente preferiría que la transposición funcione en matrices no numéricas a menos que haya una justificación realmente buena.

Creo que la idea es que la transposición es una operación matemática y, por lo tanto, solo debería definirse para vectores / matrices matemáticos.

Esto no tiene ningún sentido para mí. ¿Está esto en discusión? Realmente preferiría que la transposición funcione en matrices no numéricas a menos que haya una justificación realmente buena.

Creo que es relativamente complejo satisfacer lo que quieres _y_ tener sentido para las matemáticas. En un sentido matemático, la transposición a menudo se define intercambiando espacios vectoriales y sus espacios duales de un vector o matriz (bueno, también hay algunas complicaciones con transponer vs transponer conjugada). Para una matriz M de números regulares, tenemos la transposición como permutedims(M, (2,1)) , pero en general puede definir, por ejemplo, una matriz de "bloque" en términos de submatrices como esta:

M = [A B;
     C D]

donde A etc son las propias matrices. Tengo entendido que a Julia le gusta tener

M' = [A' C';
      B' D']

que es lo que haría con las matemáticas en lápiz y papel y, por tanto, es una "sintaxis deseable".

Esto significa que los elementos de una matriz deben aceptar ' y .' . Para los números, estos se definen como conjugación compleja y no operaciones, respectivamente. En mi humilde opinión, creo que este es un juego de palabras de conveniencia, pero funciona y, lo más importante, se implementa con reglas _simple_ ("transponer es recursiva", en lugar de necesitar clases de BlockMatrix y así sucesivamente). Pero este juego de palabras sobre la transposición se ha eliminado de los tipos que no son números en 0.5, porque no tiene mucho sentido. ¿Cuál es la transposición de Symbol ?

Si tiene _datos_, no números, entonces usar permutedims está perfectamente bien definido en su significado y comportamiento: no es recursivo. Usar un juego de palabras matemático como .' puede ahorrarle algunos caracteres al escribir, pero tendrá un _mucho_ más sentido para otra persona (que podría no estar muy familiarizado con las matemáticas o MATLAB) para leer su código si está usando reshape y permutedims según sea necesario. _Para mí, este es el_ punto más _importante_.

En mi humilde opinión, esta cuestión tan larga se trata de hacer una interfaz matemáticamente consistente para el álgebra lineal, y me parece natural que el resultado sea menos juegos de palabras matemáticos para matrices de datos.

Gracias por la atenta respuesta. Todavía estoy bastante en desacuerdo

Pero este juego de palabras sobre la transposición se ha eliminado de los tipos que no son números en 0.5, porque no tiene mucho sentido. ¿Cuál es la transposición de un símbolo?

No estoy seguro de que definir la transposición de un Symbol como no-op tenga menos sentido que definir la transposición en un número real. Entiendo que el "juego de palabras" es conveniente, pero parece que lo "correcto" sería tener la transposición definida solo para vectores y matrices y hacer que transpose(A::Matrix{Complex}) aplique conj como parte de su ejecución.

Usar un juego de palabras matemático como .' puede ahorrarle algunos caracteres al escribir, pero tendrá mucho más sentido para alguien más (que puede que no esté muy familiarizado con las matemáticas o MATLAB) para leer su código si está usando remodelar y permutar según sea necesario. Para mí, este es el punto más importante.

Estoy de acuerdo en que .' debe usarse con prudencia y que una llamada más explícita a transpose suele ser mejor. Creo que reshape y permutedims pueden requerir una cantidad no trivial de sobrecarga cognitiva para leer y codificar en primer lugar. Sea honesto, que es más rápido de analizar:

transpose([ f(x) for x = 1:length(A) ])
reshape([ f(x) for x = 1:length(A) ], 1, length(A))

Incluso en casos simples como este, debe rebotar desde el principio de la línea (para leer reshape ) hasta el final (para leer length(A) ) para comprender qué está pasando. (Luego, es probable que vuelva al medio para comprender por qué length(A) está allí en primer lugar).

Para mí, el mayor problema es que si soy una nueva Julia (lo que significa que probablemente haya usado numpy y MATLAB antes) y veo que esto funciona:

[ x for x = 1:10 ]'

Naturalmente, voy a suponer que lo mismo funcionará para matrices de cadenas, símbolos, etc. No voy a leer largas discusiones en línea para averiguar la semántica de .' : voy a probar algunas cosas y generalizar / inferir por experiencias pasadas. Quizás tener un mejor mensaje de error ayudaría aquí.

En general, no veo cómo mantener la transposición no-op en Symbol y otras entradas no numéricas interfiere con el buen marco matemático propuesto aquí (¡un objetivo digno!). Pero mantener la no operación parece inofensivo.

Pero mantener la no operación parece inofensivo.

Realmente no puede definir nada de manera significativa para Any porque todo es un subtipo de Any . La definición transpose(x)=x es completamente incorrecta para cualquier tipo de matriz y para evitar algunos de los errores silenciosos tuvimos que agregar estas definiciones . Por lo tanto, es una compensación entre la conveniencia de permitir la sintaxis relativamente extraña ' para una operación completamente no matemática y evitar errores silenciosos.

No estoy seguro de que definir la transposición de un símbolo como no-op tenga menos sentido que definir la transposición en un número real. Entiendo que el "juego de palabras" es conveniente, pero parece que lo "correcto" sería tener la transposición definida solo para vectores y matrices y hacer que transpose(A::Matrix{Complex}) aplique conj como parte de su ejecución.

No estoy completamente en desacuerdo, pero tendríamos que implementar algún tipo de BlockMatrix o tener métodos especiales definidos para Matrix{M<:Matrix} . No estoy seguro de si alguna de las dos es una idea popular o no. (Esta es una pregunta seria para aquellos que han estado siguiendo, ya que podría simplificar algunos de estos problemas relacionados).

Sea honesto, que es más rápido de analizar:

transpose([ f(x) for x = 1:length(A) ])
reshape([ f(x) for x = 1:length(A) ], 1, length(A))

El segundo, porque no me relaciono con / me gusta la transposición actual de vectores de Julia (claramente, _Tomo las transposiciones vectoriales_ demasiado _ en serio_ :)) Si tuviera que hacer el segundo, escribiría prolijamente rowvec = reshape(colvec, (1, n)) , o probablemente solo [f(x) for _ = 1:1, x = 1:n] para forzar la comprensión para hacer la forma correcta para empezar, o si realmente te gusta .' entonces map(f, (1:n).') y f.((1:n).') actualmente también funcionan.

Es un compromiso entre la conveniencia de permitir la sintaxis relativamente extraña 'para una operación completamente no matemática y evitar errores silenciosos

Si esto estaba causando errores silenciosos y otros problemas, entonces creo que probablemente voy a perder este argumento. (No veo por qué causaría errores, pero le creo). Por otro lado ...

necesitaríamos implementar algún tipo de BlockMatrix o tener métodos especiales definidos para Matrix {M <: Matrix}

Puede que me equivoque, pero creo que solo tendrías que hacer lo segundo, que me parece una opción razonable. Pero esto está empezando a superar mi sueldo.

[f (x) para _ = 1: 1, x = 1: n]

¡Me olvidé de esto! Esto es probablemente lo que terminaré haciendo. En general, sigo en desacuerdo con sus gustos por el código legible, ¡pero con el suyo propio! ¯\_(ツ)_/¯

Claramente, me tomo la transposición de vectores demasiado en serio

Si. Creo que sí. 😉

Esto (https://github.com/JuliaLang/julia/issues/16790) también haría que usar reshape en lugar de transpose poco más agradable para mí.

Podría estar equivocado, pero creo que solo necesitaría hacer el segundo (editar: tener métodos especiales de transposición definidos para Matrix{M<:Matrix} ), que me parece una opción razonable.

Desafortunadamente, ahora volvemos a la distinción entre datos y álgebra lineal. Desea que las matrices de bloques de álgebra lineal tengan transposición recursiva, pero las matrices de datos 2D genéricas de matrices de datos 2D no tengan transposición recursiva ... pero mientras Matrix{T} y Array{T,2} son lo mismo, no podemos Haz eso.

Esto (# 16790) también haría que el uso de remodelar en lugar de transponer sea un poco más agradable para mí.

¡¡Cierto!!

Desea que las matrices de bloques de álgebra lineal tengan transposición recursiva, pero que las matrices de datos 2D genéricas de matrices de datos 2D no tengan transposición recursiva

Esto no parece ser algo que obviamente quisiera ... ¿Por qué no tener dos funciones diferentes para lidiar con estos diferentes tipos de transposiciones? Supongo que me perdí la nota sobre la existencia de una distinción muy estricta entre los objetos de álgebra lineal y los objetos de datos.

Leer todo este hilo parece una tarea desalentadora, pero tal vez debería hacerlo antes de comentar más. No quiero agregar ruido desinformado.

Supongo que me perdí la nota sobre la existencia de una distinción muy estricta entre los objetos de álgebra lineal y los objetos de datos.

No hay una distinción estricta entre los objetos de álgebra lineal y el objeto de datos.
No en implementación actual, ni en uso ideal.

Si lo hubiera, entonces no se admitiría la modificación del tamaño (con push! ) o incluso los valores de los objetos de álgebra lineal (y dichos usuarios usarían StaticArrays.jl, etc.), y broadcast ser compatible con objetos de álgebra lineal.
Los Objetos de datos serían modificables, expandibles y admitirían map , (y reduce y filter ) pero no broadcast .

Pero no vivimos en un mundo donde la gente piensa en objetos binarios de datos o en objetos de álgebra lineal.
Por lo tanto, este hilo de 2.5 años y 340 comentarios.


Re la transposición no-op .
Podríamos agregar, muy arriba en la jerarquía de tipos, un tipo abstracto Scalar y Nonscalar .
Scalars todo retrocede a una transposición sin oposición.
Nonscalars no tienen respaldo, (pero por ahora recurren a una advertencia de desaprobación)

Los números, caracteres, cadenas y tal vez incluso tuplas serían Scalar y tendrían definida una transposición sin oposición. Algunos escalares, por ejemplo, Números complejos, sobrescribirán esta definición de transposición.

Las matrices (matriz, vectores y otras) serían subtipos de Nonscalar y tendrían transposición recursiva.

No estoy seguro de si eso me gusta o no.

La reciente avalancha de publicaciones repite las discusiones sobre los tipos _matrix transpose_ y "escalar" en # 18320 # 13171 # 13157 # 8974.

Realmente me gustaría disipar la noción de que la alternativa transpose(x)=x no-op es inofensiva. En mi opinión, una alternativa debería producir el resultado correcto, solo que más lentamente que un algoritmo optimizado. Es peligroso cuando el método de respaldo calcula silenciosamente la respuesta incorrecta, porque significa que el respaldo no tiene la semántica correcta: transpose(x) significa cosas diferentes según el tipo de x .

El retroceso de transposición sin operación no solo es incorrecto para matrices de matrices, sino que es incorrecto para todos los objetos tipo matriz que no son subtipos de AbstractMatrix (que por # 987 significa que tienen entradas almacenadas explícitamente, que tienen el álgebra de matrices). Aquí hay un ejemplo de niño de póster (del cual tenemos bastantes):

julia> A = rand(5,5); F = qrfact(A); R = F[:R]; Q = F[:Q] #Q is an example of a matrix-like object
5x5 Base.LinAlg.QRCompactWYQ{Float64,Array{Float64,2}}:
 -0.518817    0.0315127   0.749223    0.410014  -0.0197446
 -0.613422   -0.16763    -0.609716    0.33472   -0.3344   
 -0.0675866   0.686142    0.0724006  -0.302066  -0.654336 
 -0.582362   -0.0570904   0.010695   -0.735632   0.341065 
 -0.104062    0.704881   -0.248103    0.295724   0.585923 

julia> norm(A - Q*R) #Check an identity of the QR factorization
8.576118402884728e-16

julia> norm(Q'A - R) #Q'A is actually an Ac_mul_B in disguise
8.516860792899701e-16

julia> Base.ctranspose(Q::Base.LinAlg.QRCompactWYQ)=Q; #Reintroduce no-op fallback

julia> norm(ctranspose(Q)*A - R) #silently wrong 
4.554067975428161

Este ejemplo muestra que el hecho de que algo sea un subtipo de Any no significa que pueda asumir que es un escalar y tiene una transposición sin oposición. También ilustra por qué el problema de los padres en el OP es tan difícil de resolver. Para un objeto Q no matriz similar a una matriz, Q' no tiene un significado real como una operación de matriz, pero tiene un significado algebraico inequívoco: expresiones como Q'A están perfectamente bien definidas. Otras personas que trabajan con matrices y no con álgebra lineal simplemente quieren simulaciones permutadas no recursivas y no se preocupan por las matrices no matrices. Sin embargo, el hecho es que no se puede tener una ortografía consistente de la semántica algebraica y de intercambio de ejes para todos los tipos.

Tal vez estoy siendo denso, pero hubiera pensado que la comparación sería esta:

julia> A = rand(5,5); F = qrfact(A); R = F[:R]; Q = F[:Q]
julia> Base.ctranspose(Q::Any) = Q;
WARNING: Method definition ctranspose(Any) in module Base at operators.jl:300 overwritten in module Main at REPL[6]:1.
julia> norm(ctranspose(Q)*A - R) # still works fine
4.369698239720409e-16

Sobrescribir transpose de esta manera parece permitir transpose([ :x _=1:4 ]) , es decir, sobre lo que publiqué. Habría pensado que siempre que implemente transpose / ctranspose correctamente para todo lo que lo necesite (por ejemplo, QRCompactWYQ ), el respaldo nunca se llamaría (ya que una llamada más específica Puede ser hecho).

Su código no llama al método ctranspose que escribió (puede verificar esto con @which ). Está llamando a un método alternativo diferente en Julia v0.5 (que no existe en v0.4) que esencialmente está haciendo ctranspose(full(Q)) . Esta otra alternativa es correcta, pero anula la razón por la que tenemos este elegante tipo Q (para que se pueda multiplicar con precisión). Mi comentario de que las alternativas deben ser correctas sigue en pie.

Sí, siempre que implemente la transposición correctamente para todo lo que
lo necesita, el respaldo, por supuesto, no hace daño. El daño es que no lo haces
obtiene un error sin método si se olvida de hacer eso, pero silenciosamente el error
resultado.

Gracias @toivoh por hacer clic para mí. Realmente aprecio las explicaciones.

Pero si define una función alternativa transpose(X::AbstractMatrix) y transpose(X::AbstractVector) , entonces presumiblemente siempre obtendrá el resultado correcto (incluso si fue lento ... llamando full por ejemplo) ¿no? Y siempre puede escribir una función más especializada para hacerlo mejor / más rápido. Entonces transpose(::Any) nunca debería causar errores silenciosos que no sean los "juegos de palabras" mencionados anteriormente (es decir, cuando se manejan números Complex ... ¿tal vez otros casos de uso escalares que no conozco?)

Por razones históricas QRCompactWYQ <: AbstractMatrix , pero según # 987 # 10064 esta relación de subtipo no es correcta y debe eliminarse.

Otro ejemplo de un no-arreglo tipo matriz es IterativeSolvers.AbstractMatrixFcn , que no es un subtipo de AbstractMatrix . Para este tipo, las alternativas a las que hace referencia nunca se enviarían, lo que nuevamente fue mi punto principal.

Deberíamos continuar esta discusión en https://github.com/JuliaLang/julia/issues/13171. En realidad, el problema se trata principalmente de vectores con elementos similares a números.

Alguien del "álgebra lineal de equipo" necesita intensificar y comprometerse a hacer algo al respecto por 0.6 o será golpeado una vez más.

Entonces, para reiniciar, ¿cuál es el plan real?

Mi línea de pensamiento me lleva a: transpose(v::AbstractVector) = TransposedVector(v) donde TransposedVector <: AbstractVector . Lo único semántico que distinguirá TransposedVector de AbstractVector será cómo se comporta bajo * (y todos los A_mul_B s, \ , / , ...). Es decir, es un decorador para determinar qué índices contratar por debajo de * (etc ...). La transposición sería un concepto de álgebra lineal y si desea reorganizar una matriz de "datos", se debe recomendar reshape y permutedims .

Lo único semántico que distinguirá TransposedVector de AbstractVector será cómo se comporta bajo *

Entonces v'==v pero v'*v != v*v' ? Si bien esto puede tener sentido, también parece potencialmente confuso.

Entonces v'==v pero v'*v != v*v' ? Si bien esto puede tener sentido, también parece potencialmente confuso.

En mi opinión, esto es inevitable (aunque posiblemente desafortunado).

El dual de un vector también es un vector. La transposición también sigue siendo una estructura de datos unidimensional con elementos bien definidos que deberían poder obtener, mutar, mapear, reducir, etc.

A menos que separemos el álgebra lineal de las matrices (por ejemplo, hagamos Matrix{T} tanto un subtipo como una envoltura inmutable de Array{T,2} , con más métodos (específicos de álgebra lineal) definidos), entonces no estoy seguro de que Hay muchas opciones que son consistentes con sus propiedades de matriz y álgebra lineal.

La única pregunta confusa (para mí) es si se transmite como un vector o sobre su segunda dimensión. Si su tamaño es (1, n) entonces este cambio completo no está haciendo mucho más que afirmar que es como una matriz donde se sabe que la primera dimensión es la longitud 1 . La transposición de un Vector{T} tendría que seguir siendo un Array{T,2} (es decir, Matrix ...), sin embargo, la transposición de eso podría ser Vector nuevamente ( por ejemplo, podríamos tener v'' === v ).

¿Es mejor idea? Sería menos rompedor y aún mejoraría la semántica con el álgebra lineal. EDITAR: y se comporta de manera diferente wrt == como @martinholters muestra).

Para mí, tener TransposedVector{T <: AbstractVector{Tv}} <: AbstractMatrix{Tv} *) con size(::TransposedVector, 1)==1 pero transpose(::TransposedVector{T})::T suena como el enfoque más sensato, pero ha habido tanto debate que probablemente haya algún buen argumento en contra de esto.

*) Sé que esto es sintácticamente inválido, pero espero que la semántica deseada sea clara.

Sí, al jugar con ideas en código, encuentro que estoy de acuerdo contigo @martinholters.

Empecé en https://github.com/andyferris/TransposedVectors.jl. Todo esto se puede lograr con solo una pequeña cantidad de piratería de tipos y anulaciones de métodos de un paquete fuera de la base, e hice un paquete compatible con Julia 0.5. Si sale bien, ¿quizás podríamos portarlo a Base? (O bien, aprenda algunas lecciones).

Una gran pregunta es si podemos vivir sin un tipo CTransposedVector para la conjugación compleja, o cómo se manejará eso.

No usaría CTransposedVector por separado, porque combina dos conceptos (conjugación y remodelación) en un solo objeto. Es mucho más componible mantener esos dos conceptos implementados como entidades separadas. Por ejemplo, con MappedArrays.jl es tan fácil como

conjview(A) = mappedarray((conj,conj), A)

y también obtienes un gran rendimiento.

Bien, gracias Tim. Estaba pensando / esperando algo así; mi única preocupación era cómo garantizar que los dos contenedores siempre aparezcan en el orden "correcto", pero creo que lo que he implementado hasta ahora puede manejar eso. También necesitaríamos apoyar BLAS para todas estas combinaciones, que he logrado evitar tocar hasta ahora.

Una cosa hermosa es que un cambio separado que por defecto conj a conjview sería independiente de este cambio (creo que ambos podrían modificarse en paquetes independientes y se compondrían juntos correctamente). ¿Tiene ganas de hacer conj una vista? ¿Estamos esperando inmutables en línea que no sean isbits para hacer que tales vistas sean gratuitas? ¿O no hay necesidad de esperar?

@andyferris : Creo que su enfoque es bueno, gracias por impulsarlo. Si la implementación funciona bien, podemos usar ese código en Base. Una cosa a tener en cuenta es que también podemos querer TransposedMatrix también para https://github.com/JuliaLang/julia/issues/5332. Más allá de 1 y 2 dimensiones, no creo que las transposiciones tengan sentido, por lo que es un conjunto finito de tipos.

Ahora que hemos continuado la conversión, mencionaría nuevamente que deberíamos intentar evitar un tipo de vector transpuesto. Se ha mencionado un par de veces anteriormente, pero dudo que alguien pueda volver a leer todos los comentarios sobre este tema. La introducción de un tipo de transposición para vectores realmente complica las cosas casi sin ningún beneficio. Si realmente cree que un vector tiene una dirección, conviértalo en una matriz y las cosas simplemente funcionarán. No tiene mucho sentido tener vectores de álgebra lineal que sean diferentes de las matrices y luego introducir un nuevo tipo de envoltura para hacer que los vectores se comporten como matrices 1xn .

En este momento, la asimetría es que x' promueve x a una matriz, mientras que A*x no promueve x a una matriz. Si las operaciones de álgebra lineal fueran solo para 2D, entonces A*x deberían promover y x'x sería una matriz 1x1 . Alternativamente, podríamos dejar que los vectores sean vectores y, por lo tanto, también A*x ser un vector, pero entonces x' debería ser un error. La introducción de un vector transpuesto requerirá muchas definiciones de métodos nuevos y el único beneficio parece ser que x'x convierte en un escalar.

Creo que la transposición de la matriz es muy diferente. Nos permitirá deshacernos de todos los métodos Ax_mul_Bx y el comportamiento no es discutible de la misma manera que el comportamiento de las transposiciones vectoriales.

Realmente no veo cómo la introducción de un tipo TransposedVector causa más problemas que la introducción de un tipo TransposedMatrix.

Sí, el fuerte consenso en algún lugar en el medio de esta obra fue que la transposición vectorial es extraña y que, si se implementa, definitivamente no debería ser un subtipo de AbstractArray ; incluso no permitiría size completo. Mi resumen anterior enumera algunos de los desafíos básicos de incluir un covector, uno de los cuales es la notación (tenga en cuenta que recomendaría tomar el enfoque de Tim de mapear conj sobre la matriz en lugar de incluirlo como un tipo parámetro).

Pero hubo un llamado aún más fuerte para que la transposición vectorial sea simplemente un error. Si ese fuera el caso, entonces creo que podría vivir en un paquete.

Hacer que la transposición vectorial sea un error parece como tirar al bebé con el agua del baño. No poder escribir v' para transponer un vector sería muy, muy molesto. Realmente no entiendo qué tiene de malo tener un tipo TransposedVector que se comporta como una matriz de filas, aparte de cómo se multiplica con vectores y matrices.

Depende de la cantidad de comportamientos que desee abordar con una transposición vectorial. Si simplemente quiere v'' == v , entonces está bien para typeof(v') <: AbstractMatrix . Pero si quieres las otras cosas de las que hemos hablado en este hilo como typeof(v'v) <: Scalar o typeof(v'A) <: AbstractVector , entonces debe ser una bestia diferente y más complicada.

La idea de que TransposedVector sea una especie de Vector parece estar en la raíz de muchos problemas. Parece que sería mucho menos perturbador tener una especie de matriz y luego tener size(v.') == (1,length(v)) como lo hacemos ahora. La única diferencia sería que una doble transposición le devuelve un vector y v'v produciría un escalar. Si uno quiere tratar una transposición como un vector, puede hacerlo ya que length(v') da la respuesta correcta y la indexación lineal funciona como se esperaba.

Estoy de acuerdo 110% con @StefanKarpinski. Jugué con convertirlo en un tipo de vector, pero no tiene mucho sentido y se rompe, por el tipo de razones que se discutieron anteriormente en este hilo.

El enfoque de hacer que tenga el tamaño (1, n) significa en términos de comportamiento Array se comporta exactamente como lo hace ahora. Las _only_ operaciones que lo distinguirán de un 1-por-N Matrix es el comportamiento bajo ' , .' , * , \ . y / .

Ninguno de esos son operadores de "tipo matriz". Están ahí puramente para implementar álgebra lineal entre matrices y vectores, y provienen directamente de MATLAB ("laboratorio de matrices"). Este refinamiento final simplemente dice que el size(tvec, 1) = 1 estáticamente al compilador _y_ que quiero que v'' sea v . (Creo que es un poco como StaticArray donde una dimensión es fija y la otra tiene un tamaño dinámico).

Si simplemente quiere v '' == v, entonces está bien para typeof (v ') <: AbstractMatrix.

Correcto.

Pero si quieres las otras cosas de las que hemos hablado en este hilo como typeof (v'v) <: Scalar o typeof (v'A) <: AbstractVector, entonces necesita ser una bestia diferente, más complicada.

¿Por qué? No estamos rompiendo ninguna de sus propiedades similares a una matriz, por lo que aún puede ser una matriz. Cualquier dot-call funcionará como antes, y se eliminarán otras operaciones vectorizadas. Creo que * está "especializado" en el conocimiento de las diversas formas de Vector y Matrix y que esto se debe a que estamos tratando de emular el comportamiento del álgebra lineal escrita. Hacer que v' * v sea ​​un escalar es una matemática _muy_ estándar y mi interpretación es que la única razón por la que no ha sido así desde el principio es porque no es trivial de implementar de manera consistente (y la inspiración que Julia toma de MATLAB), pero Stefan podría aclarar eso. Hacer que el producto interno sea un escalar es el único cambio importante aquí del que hablar (hay otras cosas muy menores); no veo por qué eso solo haría que no fuera adecuado ser un Array (hay muchos tipos de Array que no tienen ' , .' , * , \ y / válidos definidos , notablemente clasificado 3+)

Uno de los problemas que encontré el año pasado fueron las ambigüedades; IIRC fueron mucho más simples de resolver cuando la transposición vectorial no era una matriz.

Sí, estoy un poco preocupado por lo profundo que llegará antes de que termine ... :)

Como nota general, sería maravilloso si / cuando hacemos Base menos monolíticos y lo factorizamos en bibliotecas estándar. Tener un paquete o módulo autónomo que solo defina AbstractArray y Array simplificaría este tipo de desarrollo.

¿Cuál fue exactamente el problema con la propuesta de @Jutho ? ¿No podría * automáticamente (conjugar) transponer el vector de la izquierda?

Si tenemos * la multiplicación de la matriz, ' la transposición de la matriz (conjugada) (deja los vectores sin cambios) y dos operadores promote que agregan un singleton final y demote que elimina un singleton final, luego podemos construir una aplicación derecha *: (n,m) -> (m,) -> (n,) , una aplicación izquierda *: (n,) -> (n,m) -> (m,) , un producto escalar *: (n,) -> (m,) -> (,) y un producto externo ×: (n,) -> (m,) -> (n,m) tal que:

(*)(A::AbstractMatrix, v::AbstractVector) = demote(A*promote(v))
(*)(u::AbstractVector, A::AbstractMatrix) = demote((promote(u)'*A)')
(*)(u::AbstractVector, v::AbstractVector) = demote(demote(promote(u)'*promote(v)))
(×)(u::AbstractVector, v::AbstractVector) = promote(u)*promote(v)'

No estoy seguro de que alguna vez necesite transponer un vector con estos operadores. ¿Me estoy perdiendo de algo?

Editar: No es exactamente la propuesta de @Jutho .

@Armavica , no estoy muy seguro de que fuera exactamente mi propuesta asignar este significado a * sino a un nuevo operador (unicode) , que actualmente equivale a dot . Además, no creo que pueda implementar el enfoque promote y demote de una manera estable de tipos.

El purista que hay en mí está de acuerdo con @andreasnoack en que se debe evitar la transposición de un vector en sí (personalmente prefiero escribir dot(v,w) sobre v'w o v'*w cualquier momento), pero también puedo apreciar la flexibilidad de hacerlo funcionar de la manera propuesta por @andyferris . Por lo tanto, no voy a agregar nada más a esta discusión y apoyar cualquier intento sensato de hacer que se ejecute una implementación real.

Correcto, @Jutho. Curiosamente, los dos no son mutuamente excluyentes: no tener una transposición en el vector definido por defecto significa que esta funcionalidad puede vivir legítimamente en un paquete, módulo o "biblioteca estándar" separado (es decir, sin "piratería de tipos").

(Yo personalmente prefiero escribir el punto (v, w) sobre v'w o v '* w en cualquier momento)

Jutho: por otro lado, apuesto a que no escribe | ψ> ⋅ | ψ> en una hoja de papel o en una pizarra, sino <ψ | ψ> (y la misma analogía se aplica a cómo dibujamos productos internos con diagramas de red tensorial).

Por supuesto que me encantaría ver la notación de dirac como la formulación estándar del álgebra lineal en todas las matemáticas o incluso en la ciencia, y por extensión en julia, pero probablemente no sucederá.

Ahora me obligas a divagar, lo que ya no iba a hacer. Ciertamente estoy de acuerdo en preferir <ψ | ψ> para el producto interno, pero eso es independiente del hecho de que no pienso en <ψ | como conjugado hermitiano de | ψ>. La conjugación hermitiana está definida para operadores. El isomorfismo natural entre los vectores y sus vectores duales asociados (covectores, funcionales lineales, ...) se conoce como teorema de representación de Riesz , aunque el primero es, por supuesto, equivalente a la conjugación hermitiana al interpretar los vectores de longitud n como mapas lineales de C a C ^ n, es decir, como matrices de tamaño (n,1) .

Por supuesto que me encantaría ver la notación de dirac como la formulación estándar del álgebra lineal en todas las matemáticas o incluso en la ciencia, y por extensión en julia, pero probablemente no sucederá.

Jajaja

Ahora me obligas a divagar, lo que ya no iba a hacer.

Lo siento ... no debería haberlo mencionado ... :)

No pienso en <ψ | como conjugado hermitiano de | ψ>. La conjugación hermitiana está definida para operadores.

Es cierto, no lo había considerado.

El isomorfismo natural entre vectores y sus vectores duales asociados (covectores, funcionales lineales, ...) se conoce como teorema de representación de Riesz, aunque el primero es, por supuesto, equivalente a la conjugación hermitiana cuando se interpretan vectores de longitud n como mapas lineales de C a C ^ n, es decir, como matrices de tamaño (n, 1).

Estoy tratando de no divagar (pero soy malo en eso), pero creo que obtenemos un poco de este último bit ya que estamos asociando AbstractVector con un 1D Array . En otras palabras, AbstractVector no significa "vector abstracto" en un sentido matemático, sino que significa "los elementos numéricos de un vector con una base predefinida". MATLAB salió de esto fácilmente porque los vectores eran de tamaño (n,1) .

Para reflexionar, ¿cómo se llama el operador (antilineal) que toma todos los tensores de la forma |a>|b><c| y los asigna a |c><a|<b| ? Siempre incluí esto junto con la conjugación del operador dual y estándar del vector, como "conjugación hermitiana", pero quizás eso sea demasiado indiferente.

Tuve una conversación con @alanedelman que cristalizó algunas cosas sobre mi posición en el # 4774:

  • introducir Covector o RowVector tipo;
  • para operaciones algebraicas lineales ( * , ' , .' , / , \ , tal vez norm ? ¿otros? ) este tipo actúa como un covector en álgebra lineal;
  • para operaciones de matriz (todo lo demás), este tipo actúa como una matriz bidimensional 1 × n;
  • en particular, size(v') de un covector es (1, length(v)) .

Este enfoque requiere clasificar todas las funciones genéricas en Base como algebraicas lineales o no. En particular, size es una función de matriz y no tiene sentido en el dominio del álgebra lineal, que esencialmente solo tiene vectores (que no tienen "forma", solo una cardinalidad de dimensiones), transformaciones lineales (que puede estar representado por matrices bidimensionales) y escalares. Un ejemplo particular que surgió fue cumsum , que actualmente opera en matrices bidimensionales como si se proporcionara un argumento de dimensión de 1. Esto es inconsistente con sum , que en lugar de tomar como valor predeterminado un argumento de dimensión de 1, devuelve la suma de toda la matriz. También evita que cumsum opere en covectors de una manera correspondiente a cómo opera en vectores. Creo que cumsum debería operar en matrices generales sumando acumulativamente en orden de columna principal N-dimensional. De manera más general, los argumentos de dimensión no deben tener el valor predeterminado 1.

Me preocuparía un poco si no hubiera una interfaz para identificar los covectors. La mayoría de las operaciones, por ejemplo, size y ndims, dirían que son como otras matrices 2d o 1d. Solo al intentar, por ejemplo, un producto escalar, verá una diferencia. AFAICT tendría que verificar si el objeto es un RowVector, pero es muy raro requerir que un objeto tenga un tipo específico para tener una determinada propiedad general.

@StefanKarpinski Estoy de acuerdo con todo eso. Especialmente que las funciones son operaciones de "matriz" o operaciones de "álgebra lineal".

Empecé con un tamaño = (1, n) TransposedVector aquí, que es exactamente como dices. Me tomó un tiempo obtener una buena cobertura de las pruebas y todas las combinaciones * , \ , / por cada posible c y t método y los métodos de mutación, evitando al mismo tiempo ambigüedades con otros métodos en base. Es un trabajo lento pero sistemático, y a partir de ahí pensé que podríamos llevarlo a la base cuando esté listo (posiblemente cambiando el nombre de las cosas).

Hay una distinción con Covector donde realmente debería ser la transposición conjugada (¡o una transformación aún más general, en el caso general!), Mientras que RowVector o TransposedVector es conceptualmente más simple.

@JeffBezanson ¿Hay algo que podamos hacer con indices() para tener una dimensión "singleton"?

pero es muy raro requerir que un objeto tenga un tipo específico para tener una determinada propiedad general.

Bien, sería genial si esto fuera un rasgo o algo así. Tenía la esperanza de que pudiéramos desenredar el álgebra lineal de las matrices de manera más general, pero eso es un gran cambio. y probablemente no podría ser ordenado sin una buena sintaxis de rasgos implementada en Julia. Creo que aquí el problema es que estamos mapeando 3 cosas (vectores, covectores y matrices) en dos tipos ( AbstractArray{1} y AbstractArray{2} ), por lo que uno de ellos (covectors) se convierte en un subtipo especial del otro.

Habría puesto AbstractTransposedVector en el paquete si hubiera pensado en un lugar donde alguien necesitaría algo más que la implementación básica de "envoltorio".

@JeffBezanson : No entiendo tu preocupación. La idea es que se comporta como una matriz abstracta 1 × n 2d excepto para las operaciones de álgebra lineal, para las cuales se comporta como el espacio dual de los vectores columna (que es isomorfo a las matrices abstractas 1 × n 2d). Si desea verificar si hay un covector, puede verificar el tipo, por ejemplo, enviando sobre él.

Una actualización sobre mis intentos de abordar esto:

TransposedVectors.jl es ahora, creo, "característica completa". Contiene toda la maquinaria necesaria para hacer lo que @StefanKarpinski ha dicho aquí: la transposición de un vector es una envoltura de un vector que se comporta como una matriz abstracta bidimensional de tamaño 1xn, pero para operaciones de álgebra lineal ( * , / , \ , ' , .' y norm ) se comporta como un vector de fila (o vector dual).

Puedes comprobarlo así:

julia> Pkg.clone("https://github.com/andyferris/TransposedVectors.jl")
...

julia> using TransposedVectors
WARNING: Method definition transpose(AbstractArray{T<:Any, 1}) in module Base at arraymath.jl:416 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/TransposedVector.jl:28.
WARNING: Method definition ctranspose(AbstractArray{#T<:Any, 1}) in module Base at arraymath.jl:417 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/TransposedVector.jl:29.
WARNING: Method definition *(AbstractArray{T<:Any, 1}, AbstractArray{T<:Any, 2}) in module LinAlg at linalg/matmul.jl:86 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/mul.jl:9.
WARNING: Method definition At_mul_B(AbstractArray{#T<:Real, 1}, AbstractArray{#T<:Real, 1}) in module LinAlg at linalg/matmul.jl:74 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/mul.jl:37.
WARNING: Method definition Ac_mul_B(AbstractArray{T<:Any, 1}, AbstractArray{T<:Any, 1}) in module LinAlg at linalg/matmul.jl:73 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/mul.jl:64.

julia> v = [1,2,3]
3-element Array{Int64,1}:
 1
 2
 3

julia> vt = v'
1×3 TransposedVectors.TransposedVector{Int64,Array{Int64,1}}:
 1  2  3

julia> vt*v
14

julia> vt*eye(3)
1×3 TransposedVectors.TransposedVector{Float64,Array{Float64,1}}:
 1.0  2.0  3.0

Puede haber algunas degradaciones del rendimiento; sería útil realizar evaluaciones comparativas. En algunos casos hará menos copias (la transposición es lenta) y en otros casos hará más copias (a veces se crea una copia conjugada de un vector (nunca una matriz) en Ac_mul_Bc por ejemplo). Y el contenedor en sí tiene un pequeño costo hasta que colocamos campos mutables en línea en inmutables (afortunadamente para mí, esto ya funciona bien con StaticArrays.jl : smile :). También está la cuestión de asegurarse de que sea una especie de StridedArray cuando sea apropiado.

Si a la gente le gusta este enfoque, podemos ver cómo hacer un PR pronto para migrar el código a Base (el paquete ya tiene pruebas unitarias y todas las ambigüedades resueltas). (Además, @jiahao había mencionado que quería experimentar con una versión de matriz abstracta unidimensional de un vector dual, ¿no está seguro de si se logró algún progreso?)

¿La gente piensa que tal RP tendría algún sentido en v0.6 sin un tipo de envoltura para matrices transpuestas? Si bien los vectores transpuestos son un cambio semántico, las matrices transpuestas son más una optimización, así que supongo que podrían entrar por separado. Por supuesto, puede parecer "extraño" si algunas transposiciones son puntos de vista y otras son copias, ¿opiniones? Otro punto a considerar es que una vez que tanto las matrices transpuestas como los vectores están dentro, se necesitaría mucho trabajo para eliminar todas las funciones At_mul_Bt , reemplazadas por métodos en * , para completar transición (¡aunque la simplificación será gratificante!) - Dudo que alguien pueda o esté dispuesto a hacer todo eso para fines de este mes ...

¡Buen trabajo, @andyferris! ¿Experimentó con una envoltura genérica perezosa Conjugate ?

@andyferris Le

Gracias chicos :)

¿Experimentó con una envoltura genérica perezosa Conjugate ?

Todavía no, aunque podría ser posible sin demasiado esfuerzo, pero para ser honesto, estaba preocupado por no terminar tanto para fines de diciembre. Mirando hacia el futuro, estaba considerando que al final podríamos tener los siguientes tipos "unionall" para enviar al álgebra lineal:

  • AbstractVector
  • Conjugate{V where V <: AbstractVector} (o Conj{V} , tal vez incluso ConjArray ? Por supuesto que aceptaría matrices de cualquier dimensión)
  • TransposedVector (o RowVector ?)
  • TransposedVector{Conjugate{V where V <: AbstractVector}}
  • AbstractMatrix
  • Conjugate{M where M <: AbstractMatrix}
  • TransposedMatrix (o simplemente Transpose{M} ?)
  • TransposedMatrix{Conjugate{M where M<:AbstractMatrix}}

(además de todos los rasgos dados del almacenamiento subyacente, como lo que ahora llamamos DenseArray y StridedArray ; espero que # 18457 también facilite un poco la expresión de estos).

Aparte de nombrar, ¿suena como un plan razonable?

En cuanto a la eliminación inmediata de bicicletas, para un PR a Base de lo que está actualmente en el paquete, preferimos TransposedVector , RowVector , una envoltura genérica Transpose para vectores y matrices , ¿o algo mas?

Creo que el comportamiento proporcionado por la implementación de @andyferris es bastante bueno y una mejora. Pero déjenme intentar expresar un poco mejor mi preocupación.

El problema es definir nuevos tipos de matrices. Por ejemplo, DArray es un subtipo de AbstractArray , por lo que un DArray también puede ser un AbstractVector o un AbstractMatrix . Idealmente, solo extenderíamos la distinción Vector / Matrix a Fila / Columna / Matriz, por lo que DArray podría ser AbstractRow , AbstractCol o AbstractMatrix . La jerarquía de tipos sería algo así como

AbstractArray
    AbstractVector
        AbstractRowVector
        AbstractColumnVector
    AbstractMatrix
    ...

Estuve hablando con

Puede haber cierta precedencia aquí, ya que no todos los AbstractVector s se pueden usar como columnas, por ejemplo:

julia> isa(1.0:10.0, AbstractVector)
true

julia> randn(10,10) * 1.0:10.0
ERROR: MethodError: no method matching colon(::Array{Float64,2}, ::Float64)

que podría abordar los problemas que tuve en https://github.com/JuliaLang/julia/issues/4774#issuecomment -59428215

¿Solo faltan paréntesis en el rango?

Eso es solo una cuestión de precedencia:

julia> randn(10,10) * (1.0:10.0)
10-element Array{Float64,1}:
 -22.4311
  ⋮

Estoy de acuerdo en que la solución óptima aquí parece requerir rasgos, pero no creo que eso deba retrasar el trabajo de

ah, buen punto. Me encantaría que la precedencia de espacios en blanco incorrecta fuera un error de sintaxis, similar a Fortress.

El grupo del MIT señaló que una forma de implementar la jerarquía de tipos que describí anteriormente es agregar un parámetro de tipo a la jerarquía de la matriz:

abstract AbstractArray{T,N,row}

type Array{T,N,row} <: AbstractArray{T,N,row}
end

typealias AbstractVector{T} AbstractArray{T,1}
typealias AbstractRowVector{T} AbstractArray{T,1,true}
typealias AbstractColVector{T} AbstractArray{T,1,false}
typealias AbstractMatrix{T} AbstractMatrix{T,2}

typealias Vector{T} Array{T,1,false}
typealias Matrix{T} Array{T,2,false}

No he resuelto todas las implicaciones de esto --- probablemente lo peor es que Array{Int,1} convierte en un tipo abstracto --- pero parece que la jerarquía de tipos y las abstracciones necesarias son exactamente correctas.

Por lo que vale, este es exactamente un caso de uso para supertipos con parámetros incompletos; pueden convertirse en parámetros implícitos por defecto.

abstract AbstractArray{T,N,row}

type Array{T,N} <: AbstractArray{T,N}
end

isrow{T,N,row}(::AbstractArray{T,N,row}) = row
isrow{T,N}(::AbstractArray{T,N}) = false

julia> isrow(Array{Int,2}())
false

Por supuesto, hace que el envío sea un poco complicado ... y puede que no valga la pena apoyarlo. Es solo una bola de saliva.

Para mi eso me parece equivalente a definir

type Array{T,N} <: AbstractArray{T,N,false}
end

Sin embargo, lo que podríamos querer aquí es algún tipo de "parámetros de tipo predeterminados", de modo que hacer el tipo Array{X,Y} donde X e Y no tienen variables libres en realidad da Array{X,Y,false} .

Otra idea: preservar la notación Householder para los productos internos v'v y el covector izquierdo multiplicar v'A haciendo que el infijo ' su propio operador en lugar de analizarlos como v'*v y v'*A . Junto con hacer v' la identidad, v*v un error y v*A un error, esto podría dar mucho de lo que se desea. Tendría que escribir los productos externos como outer(v,v) .

En tal esquema, v' sería un error o incluso un error, ya que no hay razón para transponer un vector en tal esquema, solo tendría sentido para una matriz.

@JeffBezanson Estoy de acuerdo con tener un AbstractRowVector sería lo mejor (pero honestamente no puedo pensar en un caso de uso, así que no lo implementé en el paquete). También me gustaría señalar que esto podría vivir como un subtipo de AbstractMatrix . La razón por la que me moví en esta dirección es que broadcast parece ser más un concepto central para Julia que el álgebra lineal, y la gente esperará que un vector de fila sea, bueno, ¡una fila!

Por supuesto, tener RowVector <: AbstractMatrix es un uso desafortunado de la terminología. Creo que esto se debe a que se les da el mismo nombre a matrices 2D y matrices abstractas.

He dicho esto antes, mucho más arriba, pero como este problema es tan largo, lo reformularé: en un lenguaje genérico como Julia, la propiedad "matriz de datos" tiene que ser la consideración principal para AbstractArray . Responder cómo quiere que se comporte broadcast para un vector "transpuesto" le indica si es 1D o 2D. Si quiere pensar en términos de filas y columnas, entonces los vectores de fila 1 x n más sentido. Si desea considerar los vectores duales, entonces 1D tiene más sentido, ¡y estoy contento con eso también! Pero tomar el dual de un vector es más complicado que remodelar los datos (por ejemplo, tenemos que al menos admitir la conjugación).

Supongo que la imagen de la "fila" coincidiría con las expectativas de los programadores "típicos", mientras que las personas con una formación matemática avanzada posiblemente obtendrían un mejor uso de los vectores duales, ya que es una mejor abstracción (aunque estoy seguro de que podrían simpatizar con aquellos que solo tienen conocimientos básicos de álgebra lineal). Entonces, ¿a qué audiencia se dirige Julia, personas con más habilidad matemática que muchos usuarios típicos de MATLAB, o personas con menos?

(Mi opinión fue que, dado que Julia pretendía ser un lenguaje de programación "genérico", la última audiencia era el objetivo).

Ya que estamos discutiendo posibles árboles de tipos, en el futuro, con Buffer y "listas" redimensionables, estoy imaginando un árbol abstracto de interfaces que va algo así como

AbstractArray{T,N} # interface includes broadcast, Cartesian, getindex, setindex!, etc.
    AbstractArray{T,1}
        AbstractList{T} # resizeable, overloaded with `push!` and so-on
        AbstractVector{T} # non-resizeable, overloaded for *, /, \, ', .', etc
    AbstractArray{T,2}
        AbstractRowVector{T} # non-resizeable, overloaded for *, /, \, ', .', etc
        AbstractMatrix{T} # non-resizeable, overloaded for *, /, \, ', .', etc

Por supuesto, también podríamos tener AbstractDualVector{T} <: AbstractArray{T,1} lugar de AbstractRowVector .

Tener un tipo Array flexible y concreto que se ajuste a todos estos sería difícil (y quizás innecesario). Los rasgos ciertamente nos permitirían expresar estas diferencias en las interfaces compatibles con mayor facilidad.

Tener AbstractVector como C ++ std::vector y un vector de álgebra lineal me pareció un poco descarado :) (especialmente porque la interfaz de matriz significa que nunca puede ser realmente un "vector abstracto" en el sentido de álgebra lineal (por ejemplo, sin base))

Sí, sería bueno separar el comportamiento de cambio de tamaño. Estoy a bordo para eso.

Esa jerarquía de tipos parece implicar que tendríamos tipos concretos separados de "matriz" y "matriz 2-d" en Base. Podríamos intentar solucionarlo, por ejemplo, haciendo que el constructor de Array{T,2} devuelva algún otro tipo de matriz, pero parece bastante feo. Sin embargo, tal vez estoy entendiendo mal.

Esa jerarquía de tipos parece implicar que tendríamos tipos concretos separados de "matriz" y "matriz 2-d" en Base.

Bien ... creo que para hacer esto bien, necesitaríamos rasgos. Tenga en cuenta que lo llamé un "árbol abstracto de interfaces". Intentar esto en este momento necesitaría algo feo como dijiste. Pero probablemente podríamos insertar AbstractList <: AbstractVector (y mover métodos asociados) tan pronto como Buffer haya terminado.

Por el momento, la conexión entre las interfaces compatibles y el árbol de tipos es bastante floja. Es difícil averiguar en el momento del envío si una matriz (abstracta) es mutable (es decir, admite setindex! ), si se puede cambiar de tamaño, strided solo funciona para los tipos definidos en Base , etc. Es difícil para los usuarios proporcionar una función con métodos que funcionen y sean eficientes con una amplia gama de entradas (esta es mi experiencia desde StaticArrays ).

Un pensamiento para la conversación sobre las principales regiones de la jerarquía de tipos y roles razonables para abstracciones parametrizadas. Cuando sea posible, es una buena política para Julia simplificar y aligerar cualquier separación de cómo se escribe el código para hacer lo que la experiencia pretende de lo que es la intención expresada por expertos.

Una convención que podríamos elegir utilizar hace que la práctica común use un tipo abstracto distinto de Cualquiera para crear una región ontotopológica en medio de la cual los tipos concretos concomitantes encuentran fácil el reposo compartido. Eso traería, a muy bajo costo, una mayor sensibilidad multicontextual: comprender alguna parte de la jerarquía de tipos ayuda a abordar otras partes.

Como veo esto, es un portador de simpatía generalizada. Una abstracción elucidativa ilumina como vecindario que coincide con las concreciones, las semejanzas generativas que construyen. Con las parametrizaciones que llevan la simple claridad de la intuición, Julia logra muchos más con menos tiempo sin sentido.

Bien, se agradecería cualquier comentario adicional sobre que he enumerado aquí .

(por ejemplo: ¿Es RowVector un nombre mejor que TransposedVector ? ¿Es [1 2 3] a RowVector o un Matrix ? ¿Qué es indices(row, 1) ?)

+1 para RowVector

El 20 de diciembre de 2016 a las 7:01 a. M., "Andy Ferris" [email protected] escribió:

Bien, se agradecería cualquier comentario adicional sobre
se está volviendo bastante sólido y listo para convertirse en un PR, pero hay
algunos problemas relevantes que he enumerado aquí
https://github.com/andyferris/TransposedVectors.jl/issues .

(por ejemplo: ¿RowVector es un nombre mejor que TransposedVector? ¿Es [1 2
3] ¿RowVector o Matrix? ¿Qué son los índices (fila, 1)?)

-
Estás recibiendo esto porque comentaste.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment-268170323 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAm20YYqsXmprI23GgI5PYyWStpTOq5qks5rJ309gaJpZM4BMOXs
.

Realmente me gustaría mantener la convención de filas / columnas fuera de esto, y creo que suena extraño tener Vector y RowVector (o incluso ColVector y RowVector ). +1 por TransposedVector o DualVector

@felixrehren Siento que DualVector debería tener una semántica diferente a la que he implementado, siendo principalmente unidimensional (matemáticamente, el dual de un vector es un vector) y tener otras propiedades de dualidad (por ejemplo, conjugación compleja) . Lo cual está bien, pero lo encontré un poco más difícil de implementar y un poco menos compatible con versiones anteriores.

El nombre TransposedVector está bien. Pero es un poco largo. Además, sugiere que un objeto de ese tipo solo se puede obtener transponiendo un Vector . Pero supongo que también podría hacer TransposedVector por otros medios, por ejemplo, extrayendo una fila de una matriz.

Creo que RowVector sería un buen nombre: es conciso, preciso e intuitivo. @felixrehren , creo que la imagen de fila / columna es útil. Probablemente sea incluso inevitable, ya que cada vez que realiza una concatenación u otras operaciones de matriz comunes (distintas de la multiplicación), debe pensar en qué dirección está orientado el vector.

DualVector tampoco está mal, pero CoVector sonaría menos formal.

El PR que amenacé anteriormente ahora se envía en el # 19670. Fui con RowVector por ahora.

Pero supongo que también podría hacer un TransposedVector por otros medios, por ejemplo, extrayendo una fila de una matriz.

Este es en realidad un punto de fricción: todavía no he pensado en una gran sintaxis para eso. ¿Algunas ideas?

Pero supongo que también podría hacer un TransposedVector por otros medios, por ejemplo, extrayendo una fila de una matriz.

Este es en realidad un punto de fricción: todavía no he pensado en una gran sintaxis para eso. ¿Algunas ideas?

Si bien al principio parecía atractivo que matrix[scalar,range] (y otras construcciones similares) produjeran un vector de fila, eso sería una desviación significativa de la semántica de indexación actual para matrices multidimensionales, y los casos especiales me hacen desconfiar.

En cambio, me gustaría ver que RowVector (y Vector para el caso) convierta cualquier tipo iterable en el tipo apropiado de vector. Entonces podría hacer algo como RowVector(matrix[scalar,range]) que sería bastante claro y no alteraría el comportamiento actual de la indexación de matrices.

Correcto, la fila i th se puede extraer en forma de fila por A[i,:].' , actualmente en v0.5 y continuaría haciéndolo en el futuro (para hacer un RowVector o Transpose{V<:AbstractVector} o lo que decidamos eventualmente (ver aquí la discusión en curso). Quizás esa sea la respuesta.

¿Qué hay de agregar dos nuevas funciones a Base?
row(A,i) y col(A,i)
Este último no es necesario, sino solo por simetría. Es conciso, claro y tiene tantos caracteres como A[i,:].'

@benninkrs eso tiene sentido. En contraste, mi interpretación intuitiva es la del álgebra lineal, donde un vector no tiene ninguna orientación. Todos los puntos de vista sobre esto son razonables, simplemente no me gustan Vector y RowVector juntos porque parece que el nombre mezcla un paradigma abstracto y concreto.

En este punto, creo que debemos optar por algo basado en la implementación de @andyferris o decidir no tomarnos en serio las transposiciones vectoriales. Como ejemplo de una alternativa, aquí hay un enfoque que trata los peores síntomas: deje v' como está actualmente, es decir, produciendo una matriz de filas, pero analice a'b como un operador infijo con precedencia justo debajo multiplicación. En otras palabras tendríamos los siguientes comportamientos:

  1. v' es una matriz de filas (ctranspose)
  2. v'v es un escalar (producto
  3. v'*v es un vector de 1 elemento (matriz de fila * vector)
  4. v*v' es una matriz de producto externa (vector * matriz de fila)
  5. v'A es una matriz de filas ("vecmat")
  6. v'*A es una matriz de filas (matmat)
  7. v'A*v es un escalar (matvec A * v luego producto
  8. (v'A)*v es un vector de 1 elemento (vecmat luego matvec)
  9. v'*A*v es un vector de 1 elemento (matmat luego matvec)
  10. v'' es una matriz de columna (vector ctranspose, luego matriz ctranspose)

En la configuración actual, 2 y 3 son equivalentes y 7, 8 y 9 son equivalentes, que este cambio rompe. Pero lo que es más importante, los elementos en negrita son los que la gente suele buscar, ya que son los formularios similares más cortos y convenientes, y todos hacen lo que nos gustaría que hicieran. No hay nuevos tipos, solo un nuevo operador infijo. El principal inconveniente es 10 - v'' sigue siendo una matriz de columnas. Podría decirse que esto podría verse como un beneficio, ya que el sufijo '' es el operador para convertir un vector en una matriz de columnas.

Dando un paso atrás, creo que lo que hemos aprendido es que sin una funcionalidad adicional de etiquetado de dimensiones o de arriba hacia abajo o sin tratar ≤ 2 dimensiones como fungibles como lo hace Matlab, las matrices multidimensionales realmente no se pueden hacer para encajar sin problemas con el álgebra lineal. Entonces, lo que nos queda es una cuestión de conveniencia: ¿podemos permitir que las personas expresen operaciones comunes de álgebra lineal en vectores y matrices de manera conveniente sin complicar demasiado las matrices? No estoy totalmente decidido a este enfoque, pero creo que deberíamos considerar seriamente este y otros enfoques que abordan la conveniencia sintáctica sin estropear demasiado nuestra jerarquía de tipos de matriz.

Otro enfoque sería analizar infijo a'b especialmente (justo debajo de * ) y hacer que v' devuelva un vector conjugado. De manera más general, el sufijo A' podría conjugar una matriz e invertir perezosamente sus dimensiones, mientras que A.' invertiría perezosamente las dimensiones de una matriz actuando así como la identidad en los vectores. En este esquema la lista de propiedades podría ser la siguiente:

  1. v' es un vector (conjugado)
  2. v'v es un escalar (producto
  3. v'*v es un error (recomiende v'v para el producto interno y outer(v,v) para el producto externo) †
  4. v*v' es un error (recomiende v'v para el producto interno y outer(v,v) para el producto externo) †
  5. v'A es un vector (vecmat)
  6. v'*A es un error (recomiende v'A para vecmat)
  7. v'A*v es un escalar (matvec A * v luego producto
  8. (v'A)*v es un error (recomiende v'v para el producto interno y outer(v,v) para el producto externo) †
  9. v'A'v es un escalar ( v'(A'v) - matvec conjugado y luego producto interno)
  10. v'' es un vector ( v'' === v y v.' === v )

Ahora que escribo todas estas propiedades, esta puede ser la opción preferible: todos los casos de error en realidad sirven para ayudar a las personas a descubrir y usar las sintaxis preferidas y, por supuesto, tiene la propiedad deseable v'' === v (y encaja muy bien siendo .' un operador de inversión de dimensión genérico). Tener sintaxis muy similares que son solo sutilmente diferentes probablemente sea más confuso.

† Podríamos detectarlos en el momento del análisis para detectar errores más precisos a riesgo de dar errores en los casos en que el código de usuario se haya sobrecargado ' y * para que estas operaciones funcionen. Creo que puede ser necesario tener una envoltura de conjugado perezoso para que estas recomendaciones sean correctas, pero lo necesitamos de todos modos para # 5332.

Dando un paso atrás, creo que lo que hemos aprendido es que sin una funcionalidad adicional de etiquetado de dimensiones o de arriba hacia abajo o sin tratar ≤ 2 dimensiones como fungibles como lo hace Matlab, las matrices multidimensionales realmente no se pueden hacer para encajar sin problemas con el álgebra lineal. Entonces, lo que nos queda es una cuestión de conveniencia: ¿podemos permitir que las personas expresen operaciones comunes de álgebra lineal en vectores y matrices de manera conveniente sin complicar demasiado las matrices? No estoy totalmente decidido a este enfoque, pero creo que deberíamos considerar seriamente este y otros enfoques que abordan la conveniencia sintáctica sin estropear demasiado nuestra jerarquía de tipos de matriz.

: 100:

Hacer explícitamente operaciones de matriz genérica (en lugar de álgebra lineal) postfix ' y .' evita duplicar la combinación de almacenamiento y tipos de álgebra lineal, y deja la puerta abierta para marcos que implican menos compromisos. Mientras tanto, la capacidad de esas operaciones para simular la notación de jefe de hogar en los casos más comunes debería proporcionar la mayor parte de la conveniencia deseada. También menos código y complejidad. ¡Mejor!

Un problema con v.' que no es una operación es que A .+ v.' cambiaría el significado de agregar los valores de v a cada columna de A a agregar los valores a las filas de A . Por lo general, esto dificultaría la realización de operaciones similares a filas con vectores y definitivamente necesitaría un ciclo de depreciación completo para evitar silenciosamente hacer que el código haga lo incorrecto (en casos como este, donde A resulta ser cuadrado).

En este punto, creo que debemos optar por algo basado en la implementación de @andyferris o decidir no tomarnos en serio las transposiciones vectoriales.

Me doy cuenta de que la fecha límite para la v0.6 está casi sobre nosotros, pero le advierto que no debe tirar al bebé con el agua de la bañera. En esta etapa, parece que las vistas RowVector plus mencionadas para la transposición de matrices y la conjugación de matrices nos permitirán obtener:

  • En mi opinión, tipos de álgebra lineal algo más razonables (no estamos negando la existencia de vectores duales como ahora, aunque un vector de fila podría verse como un caso especial de vector dual)
  • v'' === v
  • Algunas de las cosas en la lista de Stefan como v1'v2 es un producto escalar
  • Semántica de matriz casi compatible con versiones anteriores: por ejemplo, el resultado de size(v') se modifica, pero tenemos v'' como unidimensional
  • Lazy conj y transpose wrappers pueden aumentar el rendimiento en ciertas circunstancias y ser útiles de todos modos
  • Eliminación de todas las funciones Ac_mul_Bc a favor de * y A_mul_B! solamente (y de manera similar para \ , / ).

Hacer que todo esto funcione por Array honestamente no requeriría demasiado esfuerzo (para mí, el problema es encontrar tiempo en esta época del año y perseguir todos los otros tipos que tenemos en nuestra suite de álgebra lineal). Y el último punto sería un suspiro de alivio.

Por otro lado, en mi humilde opinión, esas reglas parecen complicar un poco el análisis y pueden ser un poco confusas y / o sorprendentes cómo componen ' con * (3, 4, 6 y 8 funcionarían con RowVector ).

Y sí, tendríamos que desaprobar v.' o algo así para resaltar posibles errores, momento en el que casi parece mejor hacer que v.' un error de método faltante (simplemente no admitiremos row / vectores duales, pero no impedirá que un paquete lo haga si lo desea)

19670 parece estar listo o cerca, si hay ganas de introducir algo en la v0.6.

BAM

Woot.

¿Fue este nuestro tema más largo?

No, # 11004 tiene más

Lo siento. Tienes razón, debería haber especificado un hilo de problema

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

helgee picture helgee  ·  3Comentarios

ararslan picture ararslan  ·  3Comentarios

yurivish picture yurivish  ·  3Comentarios

i-apellaniz picture i-apellaniz  ·  3Comentarios

tkoolen picture tkoolen  ·  3Comentarios