Julia: Prendre au sérieux les transpositions vectorielles

Créé le 10 nov. 2013  ·  417Commentaires  ·  Source: JuliaLang/julia

de @alanedelman :

Nous devons vraiment réfléchir attentivement à la manière dont la transposition d'un vecteur doit distribuer les différentes méthodes A_*op*_B* . Il doit être possible d'éviter les nouveaux types et les mathématiques laides. Par exemple, le vecteur 'vecteur donnant un vecteur (# 2472, # 2936), le vecteur' donnant une matrice et le vecteur '' donnant une matrice (# 2686) sont tous de mauvaises mathématiques.

Ce qui fonctionne pour moi mathématiquement (ce qui évite d'introduire un nouveau type) est que pour un 1-dimensionnel Vector v :

  • v' est un no-op (c'est-à-dire renvoie simplement v ),
  • v'v ou v'*v est un scalaire,
  • v*v' est une matrice, et
  • v'A ou v'*A (où A est un AbstractMatrix ) est un vecteur

Une transposition générale _N_ dimensionnelle inverse l'ordre des indices. Un vecteur, ayant un indice, doit être invariant sous transposition.

En pratique, v' est rarement utilisé isolément, et est généralement rencontré dans les produits matrice-vecteur et les produits matrice-matrice. Un exemple courant serait de construire des formes bilinéaires v'A*w et des formes quadratiques v'A*v qui sont utilisées dans des gradients conjugués, des quotients de Rayleigh, etc.

La seule raison d'introduire un nouveau type Transpose{Vector} serait de représenter la différence entre les vecteurs contravariants et covariants, et je ne trouve pas cela assez convaincant.

arrays breaking design linear algebra

Commentaire le plus utile

BAM

Tous les 417 commentaires

Par exemple, le vecteur 'vecteur donnant un vecteur (# 2472, # 2936), le vecteur' donnant une matrice et le vecteur '' donnant une matrice (# 2686) sont tous de mauvaises mathématiques.

Le double-duel d'un espace vectoriel de dimension finie lui est isomorphe et non identique. Je ne sais donc pas en quoi ce sont de mauvaises mathématiques. C'est plus que nous avons tendance à ignorer la distinction entre les choses qui sont isomorphes en mathématiques, parce que les cerveaux humains sont bons pour gérer ce genre d'ambiguïté glissante et faire ce qu'il faut. Cela dit, je conviens que cela devrait être amélioré, mais pas parce que c'est mathématiquement incorrect, mais plutôt parce que c'est ennuyeux.

Comment v' == v , mais v'*v != v*v ? Est-il plus logique que nous ne le pensions que x' * y soit son propre opérateur?

Le double-duel d'un espace vectoriel de dimension finie lui est isomorphe et non identique.

(Parlant comme moi maintenant) Ce n'est pas seulement isomorphe, c'est naturellement isomorphe, c'est-à-dire que l'isomorphisme est indépendant du choix de la base. Je ne peux pas penser à une application pratique pour laquelle il serait intéressant de distinguer ce type d'isomorphisme et une identité. OMI, le facteur de gêne vient de faire ce genre de distinction.

Est-il plus logique que nous ne le pensions que x' * y soit son propre opérateur?

C'est l'impression que j'ai eue de la discussion de cet après-midi avec @alanedelman.

Je pense que ce que Jeff demande est juste sur l'argent ... il commence à ressembler à x'_y et x_y 'fait plus
sens que jamais.

Je suis en accord violent avec @stefan. Les mauvaises mathématiques n'étaient pas censées signifier, les mauvaises mathématiques, c'était
signifiait des mathématiques ennuyeuses. Il y a beaucoup de choses qui sont techniquement correctes, mais pas très belles ....

Si nous suivons cette logique, voici deux choix que nous avons

x_x reste une erreur ..... peut-être avec une suggestion "peut-être voulez-vous utiliser un point"
ou x_x est le produit scalaire (je n'aime pas ce choix)

Si x et x' sont la même chose, alors si vous voulez que (x')*y signifie dot(x,y) cela implique que x*y est également dot(x,y) . Il n'y a pas moyen de s'en sortir. Nous pourrions faire de x'y et x'*y une opération différente, mais je ne suis pas sûr que ce soit une bonne idée. Les gens veulent pouvoir mettre cela entre parenthèses de manière évidente et que cela fonctionne toujours.

Je tiens à souligner que si nous permettons à x*x de désigner le produit scalaire, il n'y a pratiquement aucun retour en arrière. Cela va être intégré partout dans le code des gens et l'éradiquer sera un cauchemar. Donc, isomorphisme naturel ou pas, ce ne sont pas des mathématiques pures et nous devons faire face au fait que différentes choses dans un ordinateur sont différentes.

Voici une discussion pratique sur la distinction entre «tuples up» et «down tuples» que j'aime:

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

Il évite soigneusement les mots comme «vecteur» et «double», peut-être pour éviter d'ennuyer les gens. Je trouve cependant l'application aux dérivées partielles convaincante:

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

Une autre raison de distinguer M[1,:] et M[:,1] est qu'actuellement notre comportement de diffusion permet ce comportement très pratique: M./sum(M,1) est colonne-stochastique et M./sum(M,2) est ligne-stochastique . La même chose pourrait être faite pour la normalisation si nous "corrigions" la fonction norm pour permettre l'application sur les lignes et les colonnes facilement. Bien sûr, nous pourrions toujours avoir des matrices de retour sum(M,1) et sum(M,2) au lieu de vecteurs haut et bas, mais cela semble un peu décalé.

J'aime l'idée des vecteurs ascendants et descendants. Le problème est de le généraliser à des dimensions supérieures d'une manière qui n'est pas complètement insensée. Ou vous pouvez simplement faire des vecteurs un cas spécial. Mais cela ne va pas aussi.

Il est vrai que haut / bas peut être une théorie distincte. L'approche pour les généraliser semble être une structure imbriquée, qui prend les choses dans une direction différente. Il y a très probablement une raison pour laquelle ils ne les appellent pas vecteurs.

De plus, x*y = dot(x,y) rendrait * non associatif, comme dans x*(y*z) vs (x*y)*z . J'espère vraiment que nous pourrons éviter cela.

Oui. Pour moi, c'est complètement inacceptable. Je veux dire techniquement, la virgule flottante * n'est pas associative, mais c'est presque associatif, alors que ce serait tout simplement non associatif.

Nous convenons tous que x*x ne devrait pas être le produit scalaire.

La question reste de savoir si nous pouvons penser à v'w et v'*w comme produit scalaire -
J'aime vraiment ce travail de cette façon.

@JeffBezanson et moi

Une proposition est la suivante:

v' est une erreur pour les vecteurs (c'est ce que fait Mathematica)
v'w et v'*w est un produit scalaire (résultat = scalaire)
v*w est une matrice de produit externe (résultat = matrice)

Il n'y a aucune distinction entre les lignes et les vecteurs de colonne. J'ai aimé ça quand même
et était heureux de voir le précédent de Mathematica
De Mathematica: http://reference.wolfram.com/mathematica/tutorial/VectorsAndMatrices.html
En raison de la façon dont Mathematica utilise les listes pour représenter les vecteurs et les matrices, vous n’avez jamais à faire la distinction entre les vecteurs «ligne» et «colonne»

Les utilisateurs doivent être conscients qu'il n'y a pas de vecteurs de ligne .... période.

Donc si M est une matrice

M[1,:]*v est une erreur ..... (en supposant que nous allons avec M[1,:] est un scalaire
L'avertissement peut suggérer d'essayer dot ou '* ou M[i:i,:]

M[[1],:]*v ou M[1:1,:]*v est un vecteur de longueur 1 (c'est le comportement actuel de Julia de toute façon)

Concernant le problème étroitement lié dans https://groups.google.com/forum/#!topic/julia -users / L3vPeZ7kews

Mathematica compresse les sections de tableaux de type scalaire:

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}

[Modifier: formatage du code - @StefanKarpinski]

@alanedelman

en supposant que nous allons avec M [1 ,:] est un scalaire

voulez-vous dire que M [1 ,:] est juste un vecteur?

Oui désolé. Mon esprit voulait dire que M [1 ,:] traitait le scalaire 1 :-)

Mathematica utilise le point . plutôt que l'astérisque *
puis passe les 9 mètres entiers et transforme (vecteur. vecteur) un scalaire, exactement ce que nous évitons
avec l'astérisque.

Il y a sans aucun doute de nombreux problèmes avec la période, dont l'un est qu'elle ne
ressemble au "point" dans un produit scalaire, et une autre est qu'il entre en conflit avec
la lecture "pointwise op" du point,

Unicode fournit très bien un caractère nommé "l'opérateur point"
(char(8901)) que nous pouvons imaginer offrir

donc nous pourrions avoir (v ⋅ w) devenant synonyme de (v'*w)

En résumé, une proposition actuelle faisant l'objet d'un débat est

  1. L'indexation scalaire tue ainsi la dimension
    A[i,:] est un vecteur tel quel A[:,i,j]
  2. L'indexation vectorielle est épaisse
    A[ i:i , : ] ou A[ [i], : ] renvoie une matrice avec une ligne
  3. v'w ou v'*w est le produit scalaire pour les vecteurs (de même v*w' pour le produit extérieur)
  4. v' n'est pas défini pour les vecteurs (pointez l'utilisateur vers permutedims(v,1) ????)
  5. v*A renvoie un vecteur si A est une matrice
  6. v⋅w retourne également le produit scalaire (mais ne va pas aussi loin que le . de Mathematica en travaillant sur des matrices
  7. v*w n'est pas défini pour les vecteurs mais un avertissement peut avertir l'utilisateur avec de bonnes suggestions, y compris

Les conséquences sont que

une. si vous vous en tenez à tous les vecteurs étant des vecteurs de colonnes, tout fonctionne
b. si vous faites de tout une matrice, tout fonctionne certainement, et il est facile de tout faire une matrice
c. si votre esprit ne peut pas distinguer un vecteur de ligne d'une matrice à une ligne, il y a de fortes chances que vous soyez instruit
poliment et gracieusement
ré. Cette notation par points est assez agréable à regarder

La suggestion 5) me semble très étrange. Je préfère v'*A pour qu'il soit explicite que vous utilisez le vecteur dual. Ceci est particulièrement important dans les espaces vectoriels complexes où le dual n'est pas simplement une transformation de «forme».

Je veux faire écho à @StefanKarpinski qu'il serait plutôt malheureux de perdre notre comportement de diffusion concis dans tout cela. Après ce changement, quelle est la syntaxe concise pour prendre un vecteur v et normaliser les colonnes de la matrice A par ces valeurs? Actuellement, on peut utiliser A ./ v' . C'est extrêmement pratique pour la manipulation des données.

Bonnes questions

Mon schéma n'empêche pas v'*A prendre le conjugué complexe de v et de multiplier par A
et tous les autres cas que je n'ai pas encore mentionnés explicitement, mais que je pourrais facilement

nous pourrions éliminer 5
c'est peut-être souhaitable
il n'est pas conforme à ma règle de vecteur de colonne

Cette approche de la diffusion est mignonne et kludgy
Une solution est maintenant A ./ v[:,[1]]

Il a l'avantage de documenter quelle dimension est diffusée sur
et se généralise aux tableaux de dimensions supérieures

Oh et la solution v[:,[1]] a le mérite de NE PAS prendre le conjugué complexe
ce qui est probablement ce que l'utilisateur a l'intention .....

J'AIME CES DEUX EXEMPLES car le premier est un exemple d'ALGÈBRE LINÉAIRE
où un conjugué complexe est souhaité très fréquemment, mais le deuxième exemple est
un EXEMPLE DE DONNÉES MULTIDIMENSIONNELLES où nous voulons que les choses fonctionnent dans toutes les dimensions
pas seulement pour les matrices, et nous ne voulons probablement pas d'un conjuugate complexe

nécessite # 552. C'est la troisième fois qu'il apparaît au cours des deux dernières semaines.

Une autre raison de distinguer M [1 ,:] et M [:, 1] est qu'actuellement notre comportement de diffusion permet ce comportement très pratique: M./sum(M,1) est colonne-stochastique et M./sum(M, 2) est stochastique en ligne. La même chose pourrait être faite pour la normalisation si nous "corrigions" la fonction de norme pour permettre une application sur des lignes et des colonnes facilement. Bien sûr, nous pourrions toujours avoir des matrices de retour sum (M, 1) et sum (M, 2) au lieu de vecteurs haut et bas, mais cela semble un peu décalé.

Il me semble que même si le comportement de diffusion est agréable de temps en temps, vous finissez par devoir presser ces gradations supplémentaires tout aussi souvent. Ainsi, avoir à faire le contraire une partie du temps est acceptable si le reste du système est plus agréable (et je pense que la suppression des dimensions scalaires rendra le système plus agréable). Ainsi, vous aurez besoin d'une fonction comme

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

qui autorisera du code comme M ./ widen(sum(M,2),2) ou A ./ widen(v,1) (voir l'exemple @blakejohnson ci-dessus)

M [:, 0 ,:] et v [:, 0] ?????

Je suis plus avec @blakejohnson sur la question de la réduction; Personnellement, je pense que les dimensions squeeze sont plus claires que celles de widen . Je soupçonne que je regarderais en permanence les documents pour savoir si widen insère la dimension à ou après l'index indiqué, et la numérotation devient un peu plus complexe si vous souhaitez élargir plusieurs dimensions à la fois. (Que fait widen(v, (1, 2)) pour un vecteur v ?) Aucun de ces problèmes ne pose problème pour squeeze .

Que nous élargissions ou réduisions par défaut, je pense que Julia devrait suivre l'exemple de numpy lorsqu'il s'agit d'élargir et autoriser quelque chose comme v[:, newaxis] . Mais je pense que je préfère conserver les dimensions au lieu de les supprimer, il est plus difficile d'attraper un bug où vous avez accidentellement élargi le mauvais sens que lorsque vous avez serré dans le mauvais sens (ce qui donnera généralement une erreur).

Dans la liste de @alanedelman
Je sens ça

v * A renvoie un vecteur si A est une matrice

n'est pas bon.

v_A doit être une erreur si A n'est pas 1x1 (non-concordance de la plage d'index)
v'_A devrait être la bonne façon de le faire.

Une façon de gérer ce problème consiste à convertir automatiquement le vecteur v en matrice nx1 (si nécessaire)
et traitez toujours v 'comme une matrice 1xn (ne la convertissez jamais en matrice vectorielle ou nx1)
Nous permettons également de convertir automatiquement la matrice 1x1 en un nombre de scaler (si nécessaire).

Je pense que cela représente une manière cohérente et uniforme de penser l'algèbre linéaire. (bonnes mathématiques)

Une façon uniforme de gérer tous ces problèmes consiste à autoriser la conversion automatique (type?) (Si nécessaire)
entre les tableaux de taille (n), (n, 1), (n, 1,1), (n, 1,1,1) etc. (mais pas entre les tableaux de taille (n, 1) et (1, n) )
(Tout comme nous convertissons automatiquement un nombre réel en nombre complexe si nécessaire)

Dans ce cas, un tableau de taille (1,1) peut être converti en nombre (si nécessaire) (voir # 4797)

Xiao-Gang (un physicien)

Cela laisse cependant v'_A .... Je veux vraiment que v'_A * w fonctionne

Mon impression de l'algèbre linéaire chez Julia est qu'elle est très organisée comme l'algèbre matricielle, bien que des scalaires et de vrais vecteurs existent (ce que je trouve bien!)

Considérons comment gérer un produit comme x*y*z*w , où chaque facteur peut être un scalaire, un vecteur ou une matrice, éventuellement avec une transposition. L'algèbre matricielle définit le produit de matrices, où une matrice a la taille n x m . Une approche serait d'étendre cette définition afin que n ou m puisse être remplacé par absent , qui agirait comme une valeur de un en ce qui concerne le calcul du produit , mais est utilisé pour distinguer les scalaires et les vecteurs des matrices:

  • un scalaire serait absent x absent
  • un vecteur (colonne) serait n x absent
  • un vecteur de ligne serait absent x n

Idéalement, nous aimerions organiser les choses de sorte que nous n'ayons jamais besoin de représenter des vecteurs de lignes, mais il suffirait d'implémenter des opérations comme x'*y et x*y' . J'ai le sentiment que c'est la saveur du programme que beaucoup d'entre nous recherchent.

Mais je commence à soupçonner que l'interdiction des vecteurs de ligne dans ce genre de schéma aura un coût élevé. Exemple: considérez comment nous aurions besoin de mettre entre parenthèses un produit pour éviter de former un vecteur de ligne à toute étape intermédiaire: ( a est un scalaire, u et v sont des vecteurs)

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

Pour évaluer un produit x*y'*z tout en évitant de produire des vecteurs lignes, il faudrait connaître les types de facteurs avant de choisir l'ordre de multiplication! Si l'utilisateur doit le faire lui-même, cela semble être un obstacle à la programmation générique. Et je ne sais pas comment Julia pourrait le faire automatiquement de manière sensée.

Une autre raison de ne pas fixer l'ordre de multiplication à l'avance: il me semble que je me souviens qu'il y avait une idée d'utiliser la programmation dynamique pour choisir l'ordre d'évaluation optimal de *(x,y,z,w) afin de minimiser le nombre d'opérations nécessaires. Tout ce que nous faisons pour éviter de former des vecteurs de ligne va probablement interférer avec cela.

Donc pour le moment, l'introduction d'un type de vecteur transposé me semble être l'alternative la plus sensée. Cela, ou tout faire comme maintenant mais supprimer les dimensions de singleton de fin lors de leur conservation entraînerait une erreur.

La transposition n'est qu'une manière particulière de permuter les modes. Si vous autorisez v.'v est un vecteur, alors permutedims(v,[2 1]) devrait renvoyer exactement la même chose. Soit les deux renvoient un type de vecteur de ligne spécial, soit ils introduisent une nouvelle dimension.

Avoir un type spécial pour les vecteurs de ligne ne me semble pas être une bonne solution, car que ferez-vous des autres types de vecteurs mode-n, par exemple permutedims([1:4],[3 2 1]) ? Je vous exhorte à prendre également en compte l'algèbre multilinéaire avant de prendre une décision.

@toivoh a mentionné que

"Une approche consisterait à étendre cette définition afin que n ou m puisse être remplacé par absent, ce qui agirait comme une valeur de un en ce qui concerne le calcul du produit, mais est utilisé pour distinguer le scalaire et les vecteurs des matrices:

  1. un scalaire serait absent x absent
  2. un vecteur (colonne) serait nx absent
  3. un vecteur ligne serait absent xn "

En algèbre multi-linéaire (ou pour des tenseurs de rand élevés), la proposition ci-dessus correspond à utiliser absent pour représenter
de nombreux indices de rang 1, c'est-à-dire que la taille (m, n, absente) peut correspondre à (m, n), (m, n, 1), (m, n, 1,1), etc.

Si nous utilisons cette interprétation d'absence, alors 1. et 2. est OK et agréable à avoir, mais 3. peut ne pas être OK.
Nous ne voulons pas mélanger des tableaux de taille (1, n) et (1,1, n).

Je ne suis pas un spécialiste de la théorie des tenseurs, mais j'ai utilisé tous les systèmes mentionnés ci-dessus (_sans_ aucun module complémentaire) pour des projets substantiels impliquant l'algèbre linéaire.

[TL; DR: passer au RÉSUMÉ]

Voici les scénarios les plus courants dans lesquels j'ai trouvé un besoin d'une plus grande généralité dans la gestion des tableaux que les opérations matricielles-vectorielles courantes:

(1) Analyse fonctionnelle: Par exemple, dès que vous utilisez le Hessien d'une fonction à valeur vectorielle, vous avez besoin de tenseurs d'ordre supérieur pour fonctionner. Si vous écrivez beaucoup de mathématiques, il serait très difficile de devoir utiliser une syntaxe spéciale pour ces cas.

(2) Contrôle d'évaluation: par exemple, étant donné tout produit qui peut être calculé, on devrait être en mesure de calculer séparément toute sous-entité de ce produit, car on pourrait souhaiter le combiner avec plusieurs sous-entités différentes pour former différents produits. Ainsi, la préoccupation de Toivo concernant, par exemple, l'interdiction de a*u' n'est pas seulement une préoccupation de compilation, mais une préoccupation de programmation; une variante encore plus courante est le pré-calcul de x'Q pour calculer des formes quadratiques x'Q*y1 , x'Q*y2 , ... (où cela doit être fait séquentiellement).

(3) Simplifier le code: plusieurs fois, lorsque l'on traite des opérations arithmétiques mappées sur des ensembles de données multidimensionnels, j'ai trouvé que 6-7 lignes de code de boucle insondable ou de mappage de fonction peuvent être remplacées par une ou deux opérations de tableau brèves, dans les systèmes qui fournissent une généralité appropriée. Beaucoup plus lisible et beaucoup plus rapide.

Voici mes expériences générales avec les systèmes ci-dessus:

MATLAB: Le langage de base est limité au-delà des opérations vectorielles matricielles courantes, donc finissent généralement par écrire des boucles avec indexation.

NumPy: capacité plus générale que MATLAB, mais désordonnée et compliquée. Pour presque toutes les instances de problème non triviales, je devais me référer à la documentation, et même alors parfois, je devais implémenter moi-même une opération de tableau qui, selon moi, aurait dû être définie intuitivement. Il semble qu'il y ait tellement d'idées séparées dans le système que tout utilisateur et développeur aura du mal à deviner automatiquement comment l'autre va penser quelque chose. Il est généralement possible de trouver un moyen court et efficace de le faire, mais cette manière n'est pas toujours évidente pour l'écrivain ou le lecteur. En particulier, je pense que le besoin d'élargir et de dimensions singleton reflète simplement un manque de généralité dans l'implémentation pour appliquer les opérateurs (même si certains la trouvent peut-être plus intuitive).

Mathematica: Propre et très général - en particulier, tous les opérateurs pertinents sont conçus avec un comportement tenseur d'ordre supérieur à l'esprit. Outre Dot, consultez par exemple la documentation sur Transpose, Flatten / Partition et Inner / Outer. En combinant uniquement ces opérations, vous pouvez déjà couvrir la plupart des cas d'utilisation de jonglage de tableaux, et dans la version 9, ils ont même des opérations d'algèbre tensorielle supplémentaires ajoutées au langage de base. L'inconvénient est que même si la manière Mathematica de faire quelque chose est propre et a du sens (si vous connaissez le langage), elle peut ne pas correspondre évidemment à la notation mathématique habituelle pour le faire. Et bien sûr, la généralité rend difficile de savoir comment le code fonctionnera.

scmutils: Pour l'analyse fonctionnelle, il est propre, général et fournit les opérations les plus intuitives sur le plan mathématique (à la fois en écriture et en lecture) parmi celles ci-dessus. L'idée de tuple haut / bas n'est en réalité qu'une extension plus cohérente et plus générale de ce que les gens font souvent en mathématiques écrites en utilisant des signes de transposition, des conventions de différenciation et d'autres notions semi-standardisées; mais tout fonctionne. (Pour rédiger ma thèse de doctorat, j'ai fini par développer une notation cohérente et sans ambiguïté ressemblant à la notation mathématique traditionnelle mais isomorphe à la syntaxe SICM de Sussman & Wisdom.) Ils l'ont également utilisée pour une implémentation de géométrie différentielle [1], qui a a inspiré un portage vers SymPy [2]. Je ne l'ai pas utilisé pour l'analyse des données, mais je m'attendrais à ce que dans un contexte de tableau générique où vous ne vouliez qu'un seul type de tuple (comme la liste de Mathematica), vous puissiez simplement en choisir un ("up") par convention. Encore une fois, la généralité obscurcit les considérations de performances pour le programmeur, mais j'espère que c'est un domaine dans lequel Julia peut exceller.

SOMMAIRE

Je pense que le type de vecteur transposé proposé devrait être caractérisé comme le tuple "down" plus général dans scmutils, tandis que les vecteurs réguliers seraient les tuples "up". Les appeler quelque chose comme «vecteur» et «vecteur transposé» aurait probablement plus de sens pour les gens que de les appeler «haut» et «bas» (au prix de la brièveté). Cela prendrait en charge trois catégories d'utilisation:

(1) pour l'analyse des données, si les gens veulent juste des tableaux imbriqués, ils n'ont besoin que de "vecteur";
(2) pour l'algèbre linéaire de base matrice-vecteur, les gens peuvent utiliser "vecteur" et "vecteur transposé" en correspondance directe avec la convention mathématique ("matrice" équivaudrait à un "vecteur transposé" de "vecteurs");
(3) pour les opérations de tenseurs d'ordre supérieur (où il y a moins de standardisation et où les gens doivent généralement réfléchir de toute façon), l'implémentation devrait prendre en charge la généralité complète du système arithmétique de tuple à deux types.

Je pense que cette approche reflète le consensus émergent ci-dessus pour les résultats de diverses opérations, à l'exception du fait que les cas que les messages précédents considéraient comme des erreurs ( v' et v*A ) donneraient en fait un sens (et souvent utile ) résultats.

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

@thomasmcoffee ressemble à ce que vous préconisez alors une distinction explicite entre les vecteurs co- et contravariants.

Je penserais à cela comme une application commune, mais trop spécifique pour ce que je préconise: pour moi, cela a une signification géométrique impliquant une restriction aux tenseurs rectangulaires de nombres (pour les représentations coordonnées). Puisque j'imagine (sans expertise dans ce domaine) qu'une bibliothèque appropriée de fonctions d'algèbre tensorielle avec des tableaux standard suffirait généralement à cette fin, je suis d'accord avec le point d'Alan que cela seul n'est pas assez convaincant pour introduire un système à deux types dans la langue de base.

Je pense principalement à d'autres applications dépendant d'une structure imbriquée plus générale, par exemple, le calcul sur des fonctions d'arguments multiples de dimensionnalité mixte, qui serait plus difficile à développer comme "add-on" plus tard si le langage de base ne supportait pas cette distinction. Peut-être que nous voulons dire la même chose.

Le problème avec les vecteurs haut et bas est que vous devez étendre l'idée aux tableaux généraux. Sinon, les vecteurs deviennent quelque chose de spécial et séparé des tableaux, au lieu du simple cas à 1 dimension d'un tableau, ce qui conduirait à un désordre de problèmes terribles. J'ai beaucoup réfléchi à la façon de faire cela, mais je n'ai trouvé aucune solution acceptable. Si vous avez de bonnes idées sur la façon de généraliser correctement les vecteurs ascendants et descendants aux tableaux, j'aimerais les entendre.

J'essaie juste d'extrapoler cette idée. Si je comprends bien, pour utiliser un tableau pour calculer avec des vecteurs haut et bas, vous devez indiquer pour chaque dimension si elle est vers le haut ou vers le bas. En général, cela pourrait être réalisé en enveloppant un tableau dans quelque chose comme

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

UPMASK serait un masque de bits pour indiquer quelles dimensions sont en hausse. Ensuite, les opérations sur les tableaux non encapsulés pourraient être implémentées en fournissant une valeur par défaut UPMASK comme une fonction de N : les vecteurs seraient par défaut un seul haut, les matrices le premier vers le haut et le second vers le bas; alors je ne sais pas comment cela se poursuivrait raisonnablement.

Quelques pensées aléatoires:

  • Les formes quadratiques / bilinéaires seraient-elles mieux représentées par deux dimensions inférieures?
  • Si la transposition correspondait à un simple retournement de la valeur haut / bas de chaque dimension, je suppose que nous aurions également un type de matrice transposée avec la première dimension vers le bas et la seconde vers le haut.
  • Les modèles ascendants qui correspondaient à la valeur par défaut pouvaient être représentés directement par le tableau sous-jacent au lieu de l'encapsuler.

Eh bien, c'est certainement une généralisation du type Transposed , et elle a certainement du mérite. Je ne sais pas si c'est faisable.

Je pense que la suggestion de Toivo est une réalisation raisonnable de ce que je préconisais ci-dessus. Pour moi, la valeur par défaut la plus judicieuse est de continuer à alterner des directions à des ordres plus élevés: par exemple, si quelqu'un fournissait les composants d'une série de puissance sous forme de tableaux non enveloppés, cela ferait la bonne chose.

Mais après réflexion, je pense qu'il pourrait être très puissant de combiner les deux idées: (1) les distinctions entre les vecteurs haut et bas et (2) les distinctions entre les tableaux et les vecteurs. Ensuite, vous pourriez avoir des objets dont certaines dimensions sont en haut, d'autres en bas et d'autres sont «neutres». La raison de distinguer les tableaux et les vecteurs est que, sémantiquement, les tableaux sont destinés à l'organisation (collectant plusieurs choses du même type), tandis que les vecteurs sont destinés à la coordination (représentant des espaces multidimensionnels). Le pouvoir de combiner les deux distinctions en un seul objet est qu'il peut alors servir ces deux objectifs simultanément. Les dimensions neutres seraient traitées selon des règles de diffusion, tandis que les dimensions haut / bas seraient traitées selon des règles arithmétiques tensorielles.

Pour revenir à un exemple précédent, supposons que vous calculiez un certain nombre de formes quadratiques x'Q*y1, x'Q*y2, ... pour différents vecteurs y1, y2, ... . À la suite de SICM, notons les tuples vers le haut (vecteurs de colonne) par (...) et les tuples vers le bas (vecteurs de ligne) par [...] . Si vous vouliez faire tout cela en même temps, et que vous êtes coincé uniquement avec haut / bas, la manière conventionnelle serait de combiner le yi dans une matrice Y = [y1, y2, ...] utilisant un tuple bas (de up tuples), et calculez r = x'Q*Y , ce qui vous donne les résultats dans un tuple down r . Mais que faire si vous voulez multiplier chacun de ces résultats par un vecteur (colonne) v ? Vous ne pouvez pas simplement faire r*v , car vous obtiendrez une contraction (produit scalaire). Vous pouvez convertir r en un tuple up, puis multiplier, ce qui vous donne vos résultats dans un tuple up (de tuples up). Mais supposons que pour l'étape suivante, vous ayez besoin d'un tuple vers le bas? Sémantiquement, vous avez une dimension qui traverse votre calcul qui ne représente qu'une collection de choses, que vous voulez toujours diffuser; mais pour y parvenir dans le monde strictement haut / bas, vous devez continuer à faire des conversions arbitraires dépendant du contexte pour obtenir le bon comportement.

En revanche, supposons que vous ayez également des tuples neutres (tableaux), notés {...} . Ensuite, vous écrivez naturellement ys = {y1, y2, ...} comme un tableau (de tuples up), de sorte que r = x'Q*ys est un tableau, et r*v est aussi un tableau (de tuples up). Tout a du sens et aucune conversion arbitraire n'est requise.

Stefan suggère que distinguer les tableaux 1-D des vecteurs haut / bas est désastreux, mais je pense que ce problème est résolu par le fait que la plupart des fonctions fonctionnent sur des vecteurs _ou_ sur des tableaux, mais PAS sur l'un ou l'autre. (Ou, sur des matrices _ou_ sur des tableaux de vecteurs _ ou_ sur des vecteurs de tableaux _ ou_ sur des tableaux de tableaux, mais PAS _ ni l'un ni l'autre_. Et ainsi de suite.) Donc, avec des règles de conversion appropriées, je n'ai pas pensé à un cas courant qui ne le ferait pas la bonne chose automatiquement. Peut-être que quelqu'un peut?

En regardant plus en profondeur [1], j'ai découvert que les scmutils distinguent en fait ce qu'ils appellent des "vecteurs" des tuples de haut en bas sous le capot; mais actuellement les règles de conversion sont configurées de sorte que ces "vecteurs" soient mappés vers des tuples up (comme je l'avais proposé plus tôt) chaque fois qu'ils entrent dans le monde haut / bas, avec la mise en garde que "Nous nous réservons le droit de modifier cette implémentation pour distinguer Vecteurs de schéma à partir de tuples. " (Peut-être que quelqu'un sur le campus peut demander à GJS s'il avait des idées spécifiques en tête.) Le système Sage [2] sépare en grande partie la gestion des tableaux des vecteurs et des matrices (actuellement aucun support de base pour les tenseurs), et les seuls problèmes que j'ai rencontrés avec cela ont à voir avec son manque de conversion intégrée entre eux dans des cas qui auraient évidemment un sens.

[1] http://groups.csail.mit.edu/mac/users/gjs/6946/refman.txt --- à partir de "Objets structurés"
[2] http://www.sagemath.org/

Je parlais à @jiahao à la table du déjeuner et il a mentionné que l'équipe de Julia essayait de comprendre comment généraliser les opérations d'algèbre linéaire à des tableaux de dimensions plus élevées. Il y a deux ans, j'ai passé plusieurs mois à y réfléchir car j'en avais besoin pour KroneckerBio. Je voulais partager ma démarche.

Considérons juste le produit entre deux tableaux pour le moment. D'autres opérations ont une généralisation similaire. Les trois types de produits les plus courants lorsqu'il s'agit de tableaux sont le produit externe, le produit interne et le produit élémentaire. Nous pensons généralement à faire des opérations comme celle-ci entre deux objets, comme inner(A,B) ou A*B . Cependant, lorsque vous effectuez ces opérations sur des tableaux de plus grande dimension, elles ne sont pas effectuées entre les tableaux dans leur ensemble, mais entre des dimensions particulières des tableaux. Plusieurs sous-opérations externes / internes / élémentaires se produisent en une seule opération entre deux tableaux et chaque dimension de chaque tableau doit être affectée à exactement une sous-opération (soit explicitement, soit par défaut). Pour les produits intérieurs et élément par élément, une dimension à gauche doit être associée à une dimension de même taille à droite. Les dimensions extérieures du produit ne doivent pas être jumelées. La plupart du temps, l'utilisateur fait soit un produit intérieur, soit un produit élémentaire entre une paire de dimensions et un produit extérieur pour tous les autres. Le produit extérieur fait un bon défaut car il est le plus courant et ne doit pas être jumelé.

Je pense généralement que les dimensions sont nommées plutôt que classées, un peu comme les axes x, y et z d'un tracé. Mais si vous voulez que les utilisateurs puissent réellement accéder aux tableaux par indexation ordonnée (comme A[1,2,5] plutôt que A[a1=1, a3=5, a2=2] ), vous devez avoir une procédure cohérente pour classer les résultats d'une opération. Je propose de classer le résultat en listant toutes les dimensions du premier tableau suivi de toutes les dimensions du second tableau. Toutes les dimensions qui ont participé à un produit interne sont supprimées, et pour les dimensions qui ont participé à un produit élément par élément, seule la dimension du deuxième tableau est supprimée.

Je vais inventer une notation pour cela. N'hésitez pas à Juliafy. Soit A un tableau qui est a1 par a2 par a3 et soit B un tableau qui est b1 par b2 . Disons que array_product(A, B, inner=[2, 1], elementwise=[3, 2]) prendrait le produit intérieur entre les dimensions a2 et b1 , le produit élémentaire entre a3 et b2 , et le produit extérieur de a1 . Le résultat serait un tableau qui serait a1 par a3 .

Il devrait être clair qu'aucun opérateur binaire ou unaire n'aura beaucoup de sens dans le contexte des tableaux de dimension supérieure. Vous avez besoin de plus de deux arguments pour spécifier quoi faire avec chaque dimension. Cependant, vous pouvez retrouver la facilité de l'algèbre linéaire en faisant des opérateurs Matlab un raccourci pour les opérations de tableau sur les deux premières dimensions uniquement:

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

Le A.' Matlab est permute(A, B, [2,1]) où permute garde inchangée toutes les dimensions supérieures au nombre du troisième argument.

Vous pouvez choisir de lancer ou non des erreurs lorsque la dimensionnalité des tableaux est supérieure à 2 ou même pas égale à 2, comme le fait Mathematica avec des transpositions vectorielles. Si vous n'utilisez que les calculs de tableaux généraux, vous n'avez pas à décider de prendre ou non la suggestion de @wenxgwen d'interpréter tous les tableaux (n, m) comme (n, m, 1) et (n, m, 1 , 1). Ce n'est que lorsque vous utilisez les opérateurs d'algèbre linéaire ou d'autres opérateurs qui attendent un tableau ou une dimensionnalité particulière que vous devez prendre cette décision. J'aime la suggestion de @wenxgwen , car il y a peu d'inconvénients dans un langage typé dynamiquement.

J'ai écrit une description plus

Merci pour la perspective! J'ai trouvé cela assez instructif pour comprendre quel genre de bête est vraiment un produit de tableau général * array.

Peut être intéressant de croiser les propositions de multiplication de tableaux multidimensionnels avec la sémantique proposée pour un opérateur de multiplication matricielle dans PEP 0465 . En particulier:

Les entrées vectorielles 1d sont promues en 2d en ajoutant ou en ajoutant un «1» à la forme, l'opération est effectuée, puis la dimension ajoutée est supprimée de la sortie. Le 1 est toujours ajouté à "l'extérieur" de la forme: préfixé pour les arguments de gauche et ajouté pour les arguments de droite. Le résultat est que matrice @ vecteur et vecteur @ matrice sont toutes les deux légales (en supposant des formes compatibles), et toutes deux renvoient des vecteurs 1d; vector @ vector renvoie un scalaire ... Une infélicité de cette définition pour les vecteurs 1d est qu'elle rend @ non associative dans certains cas ((Mat1 @ vec) @ Mat2! = Mat1 @ (vec @ Mat2)). Mais cela semble être un cas où la praticité bat la pureté

Ducking taper en Python pose un problème particulier. Naturellement, les tableaux et les matrices doivent être interchangeables (mêmes données sous-jacentes). Mais comme Python déconseille la vérification d'un type, les matrices ne sont pas transtypées vers la bonne interface au début d'une fonction qui attend un tableau et vice versa. C'est pourquoi ils doivent avoir des caractères d'opérateur différents. Julia avec la vérification du type à l'exécution et les méthodes convert ne souffre pas de cette ambiguïté.

D'après PEP 0465:

Une infélicité de cette définition pour les vecteurs 1d est qu'elle rend @ non associatif dans certains cas ((Mat1 @ vec) @ Mat2! = Mat1 @ (vec @ Mat2))

Notamment, ce type de définition peut produire des résultats incorrects dans Mathematica, car Dot ( . ) est supposé être associatif ( Flat ) lorsqu'il est évalué symboliquement (comme avec f ci-dessous, mais pas avec 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 avec la vérification du type à l'exécution et les méthodes convert ne souffre pas de cette ambiguïté.

C'est pourquoi je pense que la bonne solution pour Julia devrait laisser les données elles-mêmes faire la distinction entre la sémantique de type tableau (pour la diffusion universelle) et la sémantique de type tenseur (pour une éventuelle contraction).

Je ne suis en aucun cas une autorité ici, mais je ne pense pas que le type de collection à dimension arbitraire générale ( Array ) devrait prendre en charge un opérateur qui fait un produit scalaire. Cet opérateur ne peut tout simplement pas être défini de manière raisonnable pour ce type car le produit scalaire peut être compris entre deux dimensions quelconques, ce qui nécessite des arguments supplémentaires qui ne peuvent pas être fournis à un opérateur binaire. Il en va de même pour toutes les opérations d'algèbre linéaire, inv , transpose , etc.

Pour opérer dans le domaine mathématique de l'algèbre linéaire, il devrait alors y avoir trois types supplémentaires, Matrix , ColumnVector , et RowVector , sur lesquels tous les opérateurs et fonctions d'algèbre linéaire normale fonctionne normalement.

Maintenant que la structure de type est bien définie, vous pouvez faciliter la tâche de l'utilisateur en ajoutant une conversion implicite pour Matrix en Array{2} , ColumnVector en Array{1} , et RowVector à Array{2} (pas sûr de celui-ci), Array{2} à Matrix , et Array{1} à ColumnVector .

Ma proposition ci-dessus (https://github.com/JuliaLang/julia/issues/4774#issuecomment-32705055) permet à chaque dimension d'une structure multidimensionnelle de distinguer si elle a neutre ("collection" / "array"), up (" colonne "), ou sémantique descendante (" ligne "). Je pense que ce que vous décrivez est alors un cas particulier.

L'avantage de cette approche générale est que même dans les calculs avec de nombreuses dimensions de données ou d'espace, vous pouvez amener les opérateurs à faire ce qu'il faut sans spécifier explicitement les dimensions sur lesquelles ils doivent agir. Je pense que nous convenons que, au moins dans Julia, il est beaucoup plus intuitif et lisible pour un utilisateur de spécifier une fois la signification des données d'entrée en choisissant des paramètres de type, que d'avoir à spécifier la signification de chaque opération en appelant chaque instance avec arguments supplémentaires donnant des indices dimensionnels. Des conversions implicites ou explicites peuvent toujours être utilisées, avec une généralité pleine dimension, dans les cas où le sens doit être changé à mi-chemin de manière inhabituelle.

@thomasmcoffee J'aime beaucoup votre proposition. J'ai implémenté quelque chose de vaguement similaire dans un DSL (il y a longtemps, très loin) avec quelques principes directeurs (aka opinions personnelles):

  1. La notion de duels comme distincts est vitale pour toute sémantique raisonnablement auto-cohérente.
  2. L'application ad hoc de l'algèbre tensorielle avec des opérateurs paramétrés (ou tout ce qui est externe aux données d'ailleurs) est esthétiquement fortement déplaisante.

Les plus grandes plaintes que j'ai reçues à l'époque (et elles étaient fortes) concernaient exactement le genre d'inconvénient que votre sémantique trivalente (en ajoutant une notion de collection neutre) résout. Agréable! Cette idée ne m'est jamais venue à l'esprit, mais elle a tellement de sens maintenant que vous l'avez diffusée. J'aimerais vraiment utiliser un tel système, et je veux dire pour un vrai travail. Ce serait bien si Julia pouvait accueillir cela!

Ce que vous semblez décrire, ce sont des tenseurs réguliers. Je doute que ce soit un cas d'utilisation assez courant à lui seul pour justifier d'être dans la bibliothèque standard, car les deux autres cas d'utilisation (collections et algèbre linéaire) sont beaucoup plus courants. Cependant, s'il pouvait être intégré de manière transparente, je le soutiendrais. Pourriez-vous donner quelques exemples de ce à quoi certaines opérations courantes ressembleraient sous ce système, telles que la multiplication de matrice vectorielle, la multiplication par tableau scalaire, la distribution de l'ajout d'un tableau sur un tableau de tableaux, etc.?

Je pense que tu as raison David. Nous parlons vraiment de deux cas d'utilisation.

Le sous-ensemble de l'algèbre linéaire est le plus souvent nécessaire par la plupart des gens lorsque vous
dire. Même là, je préconise de maintenir une distinction entre v et v '.

Ce que je voudrais vraiment (insérer ici la divulgation de la cupidité), ce sont des tenseurs avec
état de première classe (ou proche) ... proche de la vitesse native (dans le cas limite,
comparé aux performances de l'algèbre linéaire) avec une syntaxe simple, pas de surcharge
problèmes de typage, avec co / contravariance encodée dans les données, non imposée
les opérateurs. Une fois que j'ai défini la sémantique des données, les opérations doivent
travaille juste. Dactylographie de canard tensoriel.

Peut-être que les tenseurs et TDT appartiennent à un package et non au noyau, juste sur
motifs de popularité relative. Mais comme la déclaration de Julia de
l'indépendance dit, Julia est née de l'avidité. Et comme le dit Gordon Gecko,
la cupidité est bonne. :)
Le 21 mars 2014 à 03h14, "David Hagen" [email protected] a écrit:

Ce que vous semblez décrire, ce sont des tenseurs réguliers. Je doute que ce soit un
cas d'utilisation assez courant à lui seul pour justifier d'être dans la bibliothèque standard, comme
les deux autres cas d'utilisation (collections et algèbre linéaire) sont bien plus
commun. Cependant, s'il pouvait être intégré de manière transparente, je le soutiendrais.
Pourriez-vous donner quelques exemples de ce à quoi ressembleraient certaines opérations courantes
sous ce système, comme la multiplication de matrice vectorielle, tableau scalaire
multiplication, distribuant l'ajout d'un tableau sur un tableau de
tableaux, etc.?

Répondez directement à cet e-mail ou consultez-le sur Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -38262998
.

Je pense qu'une intégration transparente est certainement réalisable avec une famille de types suffisamment riche. En prolongeant https://github.com/JuliaLang/julia/issues/4774#issuecomment -32693110 de Toivo ci-dessus, cela pourrait théoriquement commencer comme ceci:

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]}

(actuellement AbstractMatrix{T} alias simplement AbstractArray{T, 2} ; potentiellement, un autre nom pourrait être utilisé ici)

À partir de là, les implémentations suivantes semblent logiques:

  1. La méthode généralisée transpose , après avoir réorganisé les dimensions et les indices UPMASK et DOWNMASK correspondants, intervertit ensuite UPMASK et DOWNMASK. Les dimensions neutres ne seraient pas affectées.
  2. Tous les sous-types AbstractArray{T, N} sont généralement convertis par défaut en sous-types alternés correspondants AbstractTensorArray{T, N, [..., false, true, false, true], [..., true, false, true, false]} dans les opérations tensorielles. Cela préserve la sémantique existante de la syntaxe de tableau spéciale de Julia pour les vecteurs et les matrices.
  3. Une méthode constructeur (de par exemple, array ) pour AbstractTensorArray est utilisé pour produire des dimensions neutres, et peut combiner d' autres AbstractTensorArray s (ou des types de convertible) pour créer un combiné AbstractTensorArray avec une dimension neutre de premier niveau.

Considérant les exemples de @drhagen :

multiplication par matrice vectorielle, multiplication par matrice scalaire

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}

(en utilisant Matrix avec une définition correspondant à la définition AbstractMatrix ci-dessus)

distribuer l'ajout d'un tableau sur un tableau de tableaux

Je suppose que cela signifie, sémantiquement, l'ajout d'un vecteur sur un tableau de vecteurs, l'ajout d'une matrice sur un tableau de matrices, et ainsi de suite:

# 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]])

Considérant mon exemple précédent de mise à l'échelle d'un vecteur v par plusieurs formes quadratiques différentes x'M*w1, x'M*w2, ... , pour un résultat 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])

Dans cette implémentation notionnelle, j'ai supposé que AbstractArray est laissé seul, et donc AbstractTensorArray forme son propre "espace" dans la hiérarchie des types. Les choses pourraient être simplifiées si toute la famille AbstractArray était simplement remplacée par AbstractTensorArray , mais c'est une autre discussion.

Dans le contexte d'un package pour quelque chose en physique quantique, j'ai joué avec la définition de mon propre type de tenseur (en fait plus d'un). Au départ, j'avais aussi une certaine notion d'indices venant de deux saveurs (haut et bas, entrant et sortant, covariant et contravariant, comme vous voulez l'appeler), ces informations étant stockées soit dans un champ du type, soit même dans un paramètre de type. Après un certain temps, j'ai décidé que c'était trop compliqué. Il est beaucoup plus facile d'associer simplement les indices du tenseur à un espace vectoriel (ce que je faisais de toute façon déjà) et de permettre à cet espace vectoriel d'avoir un dual qui est différent. En pratique, par espace vectoriel, j'entends simplement un type Julia simple qui enveloppe la dimension de l'espace et qu'il s'agisse du double ou non. Si un indice tenseur est associé à un espace vectoriel normal, c'est un index up, s'il est associé à un index dual, c'est un down index. Voulez-vous travailler avec des tenseurs / tableaux pour lesquels il n'y a pas de distinction, vous définissez simplement un type d'espace vectoriel différent qui ne fait pas la distinction entre l'espace vectoriel normal et son dual.

Dans cette proposition, vous ne pouvez contracter que des indices tensoriels qui sont associés à des espaces vectoriels qui sont doubles les uns des autres. ctranspose (= conjugaison hermitienne) mappe l'espace vectoriel de chaque indice sur son dual (avec la permutation des indices dans le cas d'une matrice, et une définition préférée pour les tenseurs d'ordre supérieur) etc.

Bien sûr, la transposition normale et la conjugaison complexe ne sont pas vraiment bien définies dans ce contexte (ce ne sont pas des concepts indépendants de la base)

De manière minimaliste, cela ressemble à ceci:

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)))

Bien sûr, vous pouvez inventer une syntaxe pour ne pas avoir à écrire constamment Space. Vous pouvez créer un type d'espace différent pour lequel duel (s) == s afin d'avoir des tenseurs qui ne font pas la distinction entre les indices haut et bas, etc. Mais bien sûr, cela ne peut toujours pas être intégré au type Array standard de Julia sans tout casser ...

Je me suis toujours demandé pourquoi il n'y avait pas de relation plus étroite entre la façon dont les tenseurs sont utilisés en ingénierie / physique et comment ils sont traités dans les logiciels mathématiques. J'ai trouvé une conversation d'échange de pile intéressante sur le sujet ... http://math.stackexchange.com/questions/412423/differences-between-a-matrix-and-a-tensor. Ici aussi, il y avait un bon article de référence.
http://www.mat.univie.ac.at/~neum/physfaq/topics/tensors

J'utilise beaucoup Matlab pour mon calcul scientifique quotidien, mais un débutant sur Julia. Ici, je remarque qu'il y a beaucoup de discussions sur la multiplication et la transposition de tableaux de haute dimension ou d'autres opérations de tableau similaires. Je suggère de jeter un coup d'œil à http://www.mathworks.com/matlabcentral/fileexchange/8773-multiple-matrix-multiplications--with-array-expansion-enabled

Il suit essentiellement la syntaxe similaire à ce que @drhagen a mentionné dans un article précédent, comme array_product (A, B, inner_A_dim = [1, 2], inner_B_dim = [3, 4]) pour un produit entre les tableaux A et B sur les dimensions intérieures données.

Il s'agit d'un package Matlab qui peut appliquer des multiplications ou des opérations de transposition sur certaines dimensions sélectionnées. Il y a un manuel dans le paquet sur la façon d'implémenter ces opérations dans Matlab, mais je pense que la théorie mathématique devrait également s'appliquer à d'autres langages. Leur idée est d'implémenter des opérations de tableau en évitant d'utiliser des boucles For, et en s'appuyant principalement sur le remodelage de tableau, etc. Donc, c'est extrêmement rapide dans Matlab. Je ne sais pas si Julia aime plus les opérations vectorisées ou les opérations dévectorisées (semble-t-il la dernière). Je pense que l'opération vectorisée est un avantage pour les opérations de tableau de grande dimension, si le noyau le prend en charge. Peut-être devrions-nous sincèrement envisager ce type d'opérations sur les tableaux à ce stade.

Pour votre référence: Une autre implémentation similaire dans Matlab pour l'opération INV est ici: http://www.mathworks.com/matlabcentral/fileexchange/31222-inversion-every-2d-slice-for-arbitrary-multi-dimension-array

Notez également qu'après la sortie du package de support d'exploitation des baies Matlab en 2005, l'enregistrement de téléchargement est maintenu à un niveau élevé jusqu'à aujourd'hui. Comme dans mon expérience pratique, les opérations de tableaux sont très fréquemment utilisées en physique et dans d'autres domaines. Je dirais que si Julia a des fonctions similaires pour exploiter des tableaux de tailles arbitraires, le jeu deviendra très intéressant!

un autre vote ici pour la solution proposée par @alanedelman vers le haut. voici un exemple motivant.

à l'heure actuelle, une tranche de ligne est un tableau 2d, tandis qu'une tranche de colonne est un tableau 1d; qui est étrangement asymétrique et moche:

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 particulier, cela signifie que je ne peux pas multiplier une ligne par une colonne et extraire un nombre sans faire une manipulation terriblement laide comme

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

Ce n'est pas une proposition cohérente à moins que vous ne fassiez de '* un opérateur spécial, ce qui est assez douteux, puisque x'*y et (x')*y ne signifient pas la même chose. De plus, cela rendrait la multiplication non associative.

je comprends les difficultés avec x_y 'et y'_x. Il vaut peut-être mieux traiter
produits intérieurs et extérieurs en tant qu'opérations séparées, en utilisant par exemple dot (). (Peut-être
utilisant également cdot?)

Mais quels sont les arguments en faveur d'avoir une tranche le long du premier
dimension renvoie un objet dont la dimension est différente d'une tranche le long
la deuxième dimension? Par souci de cohérence, il semble que chaque fois que vous coupez,
la dimension de l'objet résultant doit être diminuée de un.

Le mercredi 16 juillet 2014 à 20:17, Stefan Karpinski [email protected]
a écrit:

Ce n'est pas une proposition cohérente sauf si vous faites '* un opérateur spécial, qui
est assez douteux, puisque x'_y et (x ') _ y ne signifient pas la même chose.
De plus, cela rendrait la multiplication non associative.

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -49254346.

Madeleine Udell
Doctorant en génie informatique et mathématique
Université de Stanford
www.stanford.edu/~udell

@madeleineudell , je suis d'accord avec vous, mais c'est un problème différent, voir # 5949. Bien que cette question semble close, je ne me souviens pas qu'il y avait un accord ou une conclusion claire.

Une fois que nous passerons aux vues matricielles, il deviendra plus facile d'explorer ces directions. En particulier, dire slice(A, i, :) vous donne le comportement que vous voulez. (Il le fait maintenant, mais au prix de l'introduction d'un type plus lent, le SubArray.)

D'un point de vue purement mathématique, tous les problèmes présentés ici proviennent d'un amalgame (et d'une confusion entre) ce que nous entendons par tableaux et ce que nous entendons par vecteurs / tenseurs / matrices. Les tableaux, d'un point de vue conceptuel, sont simplement des listes (ou, dans le cas de tableaux à n dimensions, des listes de listes). En tant que tel, il n'y a pas de spécification naturelle pour les opérations telles que la multiplication de tableaux, la transposition, etc. de manière naturelle, les opérations telles que les produits scalaires ne peuvent pas être.

Comme mentionné ci-dessus, les vecteurs et les tenseurs sont des objets géométriques, et s'il est possible de les représenter à l'aide de tableaux, ces représentations ne contiennent pas la même richesse de structure que les objets mathématiques qu'ils représentent. La transposition d'un tableau à 1 dim est un no-op; la transposée d'un vecteur est son dual. La transposition d'un tableau à 2 dimensions peut être définie de manière unique et naturelle comme la permutation de ses dimensions, mais ce n'est généralement pas vrai pour les tenseurs: alors que le cas naturel est vrai pour les tenseurs de rang (1,1) Le tenseur de rang (2,0) se transpose en un tenseur de rang (0,2). Encore une fois, en traitant les tenseurs comme des tableaux, l'information géométrique qui fait des tenseurs des tenseurs est perdue.

Cela est important lors de la définition d'opérations telles que les produits scalaires. Un produit scalaire a une signification géométrique spécifique (la projection d'un vecteur sur l'espace dual défini par un deuxième vecteur), et donc une définition cohérente des produits scalaires nécessite la préservation des informations géométriques contenues dans les vecteurs. L'utilisation de certaines hypothèses pourrait permettre d'utiliser des tableaux tout en couvrant la majorité des cas d'utilisation, mais ces hypothèses sont désordonnées (comme le montrent les différentes propositions de ce fil) et rendent en fait les choses plus difficiles pour quiconque a besoin de la structure plus riche des tenseurs .

Considérez donc ceci comme un vote fort en faveur de la suggestion de thomasmcoffee d'inclure un type AbstractTensor plus riche. Ma préférence personnelle serait que des opérations telles que la transposition et les produits scalaires ne soient même pas définies pour les tableaux, mais comme je soupçonne que la plupart des gens ne partageraient pas ce point de vue, je voudrais au moins pouvoir créer de vrais tenseurs si le besoin s'en faisait sentir.

L'implication pratique de cette perspective semble être que les tableaux devraient être identifiés avec un sous-ensemble de tenseurs, et la transposition d'un tableau 1-d devrait donner un DualVector ou peut-être une erreur. Mon avis est que cela est analogue aux opérations sur des nombres réels qui donnent des nombres complexes.

Ma perspective serait que la famille générale AbstractArray, un conteneur de données (multidimensionnel), est suffisamment générale pour faire partie intégrante de tout langage de programmation technique. Un tenseur suivant des règles mathématiques strictes, même si je m'en soucie beaucoup, est un bon objet pour un package dédié. En fait, je travaille sur quelque chose dans le sens spécifié par @jdbates dans https://github.com/Jutho/TensorToolbox.jl . Il est jusqu'à présent non documenté et largement non testé. Je l'ai écrit pour les choses dont j'ai personnellement besoin en physique quantique de nombreux corps, mais j'espère qu'il est construit d'une manière suffisamment générale et extensible pour être utile à la plus grande communauté de mathématiciens et de physiciens qui se soucient de travailler avec des tenseurs.

Pour donner quelques détails (copié du forum JuliaQuantum): j'ai décidé de définir une nouvelle hiérarchie de types pour les tenseurs, qui est indépendante du type AbstractArray de Julia (bien que le Tensor de base ne soit qu'un wrapper pour Array). Cette hiérarchie de types est censée fonctionner de manière légèrement plus formelle. Les indices tensoriels sont associés à des espaces vectoriels (désormais appelés espaces d'index), et si le type d'espace vectoriel auquel est associé l'indice tensoriel est différent de son dual, cela correspond à un tenseur qui distingue les indices covariants et contravariants.

Donc, la première partie du package est la partie abstraite pour définir les espaces vectoriels, où je vais faire correspondre la hiérarchie de type des objets Julia à la hiérarchie mathématique des espaces vectoriels. Un espace vectoriel général V se décline en quatre variétés, correspondant à la théorie de la représentation du groupe linéaire général sur V, c'est-à-dire V lui-même (représentation fondamentale), conj (V), dual (V) et dual (conj (V)). Pour les espaces vectoriels réels, conj (V) = V et il n'y a que V et dual (V), correspondant aux vecteurs contravariants et covariants. Ensuite, il y a les espaces de produits internes, et au niveau supérieur de la hiérarchie se trouvent les espaces euclidiens, qui sont des espaces de produits internes avec un produit interne euclidien standard (c'est-à-dire une base orthogonale). En physique, il est également utile de penser aux espaces vectoriels qui sont décomposés en différents secteurs, c'est-à-dire qu'ils sont gradés par exemple par des représentations irréductibles d'actions de symétrie.

Les tenseurs sont des objets vivant dans (un sous-espace de) le produit tensoriel de certains espaces vectoriels élémentaires. Cependant, en dehors du Tensor standard, qui est un objet vivant dans l'espace produit tensoriel de ses espaces d'index, on pourrait construire des tenseurs qui vivent par exemple dans le secteur invariant d'un produit tensoriel d'espaces gradés par irreps, le sous-espace symétrique ou antisymétrique de un produit tensoriel d'espaces identiques, ... On pourrait avoir des espaces vectoriels fermioniques comme espaces d'indices, ce qui implique qu'une permutation des indices tensoriels induira certains changements de signe en fonction des secteurs de parité, etc ...

Ensuite, on suppose qu'il y a certaines opérations définies sur des tenseurs, dont la plus importante est la contraction des tenseurs, mais aussi, par exemple, des factorisations orthogonales (décomposition de valeurs singulières) etc. Ils méritent un type spécial dans la mesure où on ne veut généralement pas les encoder complètement en tant que matrice, mais plutôt de manière à ce que le produit vectoriel de matrice puisse être calculé efficacement, pour une utilisation dans des méthodes itératives (Lanczos, etc.). Mes deux packages existants jusqu'à présent (TensorOperations.jl et LinearMaps.jl) implémentent cette fonctionnalité pour les tableaux standard, la boîte à outils tensor en cours de construction les surchargerait / les redéfinirait pour la nouvelle hiérarchie AbstractTensor.

J'espère que ce paquet est suffisamment général pour qu'il soit également utile pour la communauté physique / mathématique au sens large. Par exemple, si quelqu'un vient créer un package pour travailler avec des variétés, il pourrait alors définir un espace vectoriel TangentSpace comme un sous-espace de l'InnerProductSpace abstrait, et il peut alors créer immédiatement des tenseurs vivant dans le produit tensoriel de quelques espaces tangents et cotangents. En fait, je pense à diviser la partie pour définir les espaces vectoriels dans un package séparé, qui pourrait devenir un package pour définir des structures / objets mathématiques.

Enfin, l'interopérabilité avec julia standard vient de l'appel de tensor sur un Array standard, qui l'enveloppe dans un objet de type Tensor avec les indices associés aux espaces de type CartesianSpace . Il s'agit de l'espace vectoriel réel standard R ^ n avec produit euclidien, où il n'y a pas de distinction entre l'indice covariant et contravariant. Je pense que cela implique le mieux ce qu'est un Julia Array standard.

@JeffBezanson , je suis ambivalent concernant le traitement des tableaux comme des sous-ensembles de tenseurs. Aucune information n'est perdue de cette façon, mais en même temps, il existe de multiples interprétations possibles pour les tableaux, et l'interprétation du tenseur n'a pas toujours (ou même généralement) de sens. Considérez les images: une image peut être considérée comme un champ à valeurs vectorielles sur une variété (typiquement 2D). Limiter ce champ à une grille rectangulaire vous donne une structure que, naturellement, vous voudriez représenter à l'aide d'un tableau 3D. Cependant, en réalité, il ne s'agit que d'un mappage de l'espace des points de grille dans l'espace vectoriel {R, G, B}, donc la signification géométrique des deux premières dimensions (les étiquettes x et y de la grille) est différente de celle signification géométrique de la troisième dimension (qui est, en fait, un vecteur).

Je ne suis pas opposé à la suggestion de @Jutho de séparer la mécanique des tenseurs dans un package séparé. Il a probablement raison de dire que le nombre d'utilisateurs qui ont besoin de la mécanique complète des tenseurs est beaucoup plus petit que le nombre de personnes qui veulent juste des opérations de tableau simples. La question que nous essayons vraiment de se poser ici est "dans quel domaine doit tomber l'algèbre linéaire?"

La machinerie de l'algèbre linéaire est un sous-ensemble assez substantiel de la machinerie de l'algèbre tensorielle que, dans mon esprit du moins, il n'a aucun sens d'implémenter la première sans implémenter également la seconde. Les opérations comme v'M sont représentées de manière plus concise et cohérente si nous avons une notion de vecteurs co- et contravariants, mais cela nous place déjà en grande partie vers des opérations tenseur générales.

Je conviens avec vous que cela est conceptuellement similaire aux opérations sur des nombres réels qui renvoient des nombres complexes.

Considérez les images: une image peut être considérée comme un champ à valeurs vectorielles sur une variété (typiquement 2D). Limiter ce champ à une grille rectangulaire vous donne une structure que, naturellement, vous voudriez représenter à l'aide d'un tableau 3D. Cependant, en réalité, il ne s'agit que d'un mappage de l'espace des points de grille dans l'espace vectoriel {R, G, B}, donc la signification géométrique des deux premières dimensions (les étiquettes x et y de la grille) est différente de celle signification géométrique de la troisième dimension (qui est, en fait, un vecteur).

Bien que cela n'aborde pas ou ne retire pas de votre message global, https://github.com/timholy/Images.jl/pull/135 travaille à une mise en œuvre de cette idée pour les images. J'espère que cela facilitera également la gestion des tenseurs de structure de couleur , que je cherche à utiliser pour un projet.

Le 23 août 2014, à 20h36, jdbates [email protected] a écrit:

@JeffBezanson , je suis ambivalent concernant le traitement des tableaux comme des sous-ensembles de tenseurs. Aucune information n'est perdue de cette façon, mais en même temps, il existe de multiples interprétations possibles pour les images, et l'interprétation tenseur n'a pas toujours (ou même généralement) de sens. Considérez les images: une image peut être considérée comme un champ à valeurs vectorielles sur une variété (typiquement 2D). Limiter ce champ à une grille rectangulaire vous donne une structure que, naturellement, vous voudriez représenter à l'aide d'un tableau 3D. Cependant, en réalité, il ne s'agit que d'un mappage de l'espace des points de grille dans l'espace vectoriel {R, G, B}, donc la signification géométrique des deux premières dimensions (les étiquettes x et y de la grille) est différente de celle signification géométrique de la troisième dimension (qui est, en fait, un vecteur).

Je suis d'accord que les tenseurs ne remplacent pas les tableaux. Cet exemple ci-dessus est en effet une structure mathématique différente (ie un fibré vectoriel ou plus généralement un fibré tensoriel) dont la représentation peut également être donnée sous forme de tableau multidimensionnel en choisissant une grille pour les coordonnées de la variété et une base pour la partie d'espace vectoriel. Ainsi, en effet, vous pouvez avoir différents objets / structures mathématiques qui sont bien définis de manière indépendante des coordonnées / indépendante de la base mais qui peuvent être représentés (après avoir choisi un système de coordonnées ou une base) comme un tableau multidimensionnel. Les tableaux multidimensionnels ne sont donc certainement pas limités à la représentation des tenseurs. L'inverse échoue également, car tous les tenseurs n'ont pas une représentation pratique utilisant un tableau multidimensionnel. Ce n'est le cas que lorsque vous utilisez une base particulière connue sous le nom de base de produit, qui est obtenue en prenant le produit direct de toutes les combinaisons possibles des vecteurs de base individuels des espaces vectoriels impliqués dans l'espace des produits tensoriels. Dans certains cas, par exemple lors de l'utilisation de tenseurs dans un sous-espace invariant par symétrie de l'espace des produits tensoriels, une telle représentation n'est plus possible et vous devez définir une base différente pour l'espace complet, par rapport auquel le tenseur est simplement représenté comme une longue liste unidimensionnelle de nombres.

Je ne suis pas opposé à la suggestion de @Jutho de séparer la mécanique des tenseurs dans un package séparé. Il a probablement raison de dire que le nombre d'utilisateurs qui ont besoin de la mécanique complète des tenseurs est beaucoup plus petit que le nombre de personnes qui veulent juste des opérations de tableau simples. La question que nous essayons vraiment de se poser ici est "dans quel domaine doit tomber l'algèbre linéaire?"

La machinerie de l'algèbre linéaire est un sous-ensemble assez substantiel de la machinerie de l'algèbre tensorielle que, dans mon esprit du moins, il n'a aucun sens d'implémenter la première sans implémenter également la seconde. Les opérations comme v'M sont représentées de manière plus concise et cohérente si nous avons une notion de vecteurs co- et contravariants, mais cela nous place déjà en grande partie vers des opérations tenseur générales.

Je conviens avec vous que cela est conceptuellement similaire aux opérations sur des nombres réels qui renvoient des nombres complexes.

-
Répondez directement à cet e-mail ou affichez-le sur GitHub.

il existe plusieurs interprétations possibles pour les tableaux, et l'interprétation des tenseurs n'a pas toujours (ou même généralement) de sens. Considérez les images: une image peut être considérée comme un champ à valeurs vectorielles sur une variété (typiquement 2D). Limiter ce champ à une grille rectangulaire vous donne une structure que, naturellement, vous voudriez représenter à l'aide d'un tableau 3D. Cependant, en réalité, il ne s'agit que d'un mappage de l'espace des points de grille dans l'espace vectoriel {R, G, B}, donc la signification géométrique des deux premières dimensions (les étiquettes x et y de la grille) est différente de celle signification géométrique de la troisième dimension (qui est, en fait, un vecteur).

C'était juste ce genre de distinction que j'essayais de capturer dans la proposition notionnelle AbstractTensorArray de https://github.com/JuliaLang/julia/issues/4774#issuecomment -38333295 en autorisant à la fois un tableau et un tenseur - dimensions semblables. Dans le cadre de ce schéma, je m'attendrais à représenter votre exemple comme

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

de sorte que les dimensions x, y et RVB soient respectivement "bas", "haut" et "neutre". Les opérations géométriques (par exemple, les transformations affines) pourraient alors gérer les dimensions des coordonnées de la grille à la manière d'un tenseur tout en mappant les valeurs RVB à la manière d'un tableau. (Si vous souhaitez plus tard traiter les valeurs RVB géométriquement, vous devrez modifier explicitement le masque à cette fin, mais je suppose que (a) il est moins courant que deux saveurs différentes d'opérations géométriques soient appliquées à différents sous-espaces de la même table de données, et (b) dans cette situation, une conversion explicite _améliorerait_ probablement la clarté du code.)

Je n'avais pas envisagé les représentations conjuguées que mentionne @Jutho , mais il me semble que cette généralisation pourrait être gérée en étendant davantage la même approche de masquage, pour des espaces complexes.

La question que nous essayons vraiment de se poser ici est "dans quel domaine doit tomber l'algèbre linéaire?"

Une fois qu'une conception est établie pour la manière dont les opérations de type tableau et de type tenseur jouent ensemble, les entités pour l'algèbre linéaire peuvent simplement être définies par des cas spéciaux (comme les alias que j'ai utilisés ci-dessus), de sorte que l'utilisateur d'algèbre linéaire pure puisse être inconscient de toute la hiérarchie de tenseur généralisée jusqu'à ce qu'elle soit nécessaire (mais n'aura pas à réécrire les choses si et quand c'est le cas). Je ne verrais donc aucun problème (sauf peut-être gonflement) à mettre le tout dans Base.

de sorte que les dimensions x, y et RVB soient respectivement "bas", "haut" et "neutre". Les opérations géométriques (par exemple, les transformations affines) pourraient alors gérer les dimensions des coordonnées de la grille à la manière d'un tenseur tout en mappant les valeurs RVB à la manière d'un tableau. (Si vous souhaitez plus tard traiter les valeurs RVB géométriquement, vous devrez modifier explicitement le masque à cette fin, mais je suppose que (a) il est moins courant que deux saveurs différentes d'opérations géométriques soient appliquées à différents sous-espaces de le même tableau de données, et (b) dans cette situation, une conversion explicite améliorerait probablement la clarté du code.)

Je pense que vous mélangez quelque chose ici. Dans la discussion ci-dessus, il a été en fait expliqué que les coordonnées x et y ne portaient pas l'interprétation de l'espace vectoriel, car elles peuvent correspondre à des coordonnées sur une variété incurvée arbitraire, pas nécessairement un espace plat. C'est la dimension RVB qui a été donnée à l'interprétation vectorielle, bien que ce ne soit pas non plus le meilleur choix, car je semble me souvenir (je n'ai pas un fond décent en traitement d'image) que l'espace colorimétrique est également plutôt incurvé. Aussi, même dans le cas où le domaine (x et y) forme un espace vectoriel, pourquoi x et y seraient-ils en haut et en bas, ou était-ce juste un exemple de votre notation?

Quoi qu'il en soit, j'ai également commencé avec TensorToolbox.jl en désignant les indices covariants et contravariants comme une sorte de paramètres ou de masque, mais cela devient rapidement un cauchemar complet, c'est pourquoi je suis passé à une représentation où chaque tenseur est un élément d'un espace vectoriel. , et pour effectuer des opérations, il faut vérifier que les espaces correspondent, tout comme vous devez vérifier que les tailles correspondent lorsque vous effectuez des opérations avec des tableaux.

les coordonnées x et y ne portaient pas l'interprétation de l'espace vectoriel

Désolé, j'ai écrasé "grille rectangulaire" --- je suppose que @jdbates voulait dire exactement ce qu'il a dit. Mais ne parlons-nous pas simplement de remplacer les produits dot par des produits internes généralisés? (Pardonnez-moi si je mal compris, je passe presque tout mon temps dans l'espace euclidien :-)

chaque tenseur est un élément d'un espace vectoriel

Cela semble être une bonne idée - je serais intéressé de voir quelques exemples de la façon dont cela fonctionne pour l'utilisateur (je n'ai pas été très loin en lisant le code).

J'ai une nouvelle proposition pour ce numéro.


(1) Tranchage de type APL.

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

En particulier, cela signifie que les "tranches singleton" - c'est-à-dire les tranches où l'index est scalaire ou de dimension zéro - sont toujours supprimées et que M[1,:] et M[:,1] sont tous les deux des vecteurs, au lieu d'un vecteur tandis que l'autre est une matrice de lignes, ou toute autre distinction du genre.


(2) Introduisez les types de wrapper Transpose et ConjTranspose pour les vecteurs et les matrices. En d'autres termes, quelque chose comme ceci:

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)

et toutes les méthodes appropriées pour les faire fonctionner comme il se doit pour les vecteurs et les matrices. Nous pouvons vouloir le limiter à ne travailler que pour les vecteurs et les matrices, car on ne sait pas ce qu'une transposition générale devrait signifier pour des dimensions arbitraires (bien que l'inversion des dimensions soit tentante). Lorsque vous écrivez a' vous obtenez ConjTranspose(a) et de même v.' produit Transpose(a) .


(3) Définir diverses méthodes spécialisées pour les vecteurs et matrices transposés (conjugués), telles que:

*(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., y compris le remplacement de toutes les horribles fonctions At_mul_B et une analyse spéciale par une construction de transposition paresseuse (conjuguée) suivie d'une distribution sur les types Transpose et ConjTranspose .


(4) Restreindre les opérations de diffusion aux cas où les arguments sont des scalaires ou des tableaux avec le même nombre de dimensions. Ainsi, ce qui suit, qui fonctionne actuellement comme indiqué, échouera:

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

Au lieu de cela, vous devrez faire quelque chose comme ceci:

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

Je pense que cette proposition résout tous les problèmes majeurs auxquels nous sommes actuellement confrontés:

  1. comportement de découpage symétrique - les cotes de fin ne sont plus spéciales.
  2. v'' === v .
  3. v' == v .
  4. v'w est le produit scalaire de v et w - en particulier, c'est un scalaire, pas un vecteur à un élément.
  5. v*w' est le produit extérieur de v et w .
  6. M*v est un vecteur.
  7. M*v' est une erreur.
  8. v'*M est un vecteur transposé.
  9. v*M est une erreur.
  10. At_mul_B opérateurs

: +1: à tout cela. J'ai travaillé sur 2 et 3 dans # 6837, mais je ne l'ai jamais terminé. @simonbyrne l'a également examiné.

+1 aussi. On dirait que cela offrirait un comportement assez cohérent partout.

La seule partie vraiment perturbatrice de cette proposition serait en fait que M[1,:] est un vecteur implicitement vertical plutôt qu'une matrice de lignes explicitement horizontale. Sinon, c'est en fait un ensemble de changements assez fluide et non perturbateur (on l'espère). La principale épiphanie (pour moi) était que le comportement de découpage APL pouvait être combiné avec des transpositions paresseuses. Si nous obtenons l'adhésion, nous pouvons élaborer un plan et répartir le travail. J'espère vraiment que les transpositions paresseuses et les fonctions par étapes permettent une réduction et une simplification du code.

Oui s'il vous plaît! La transposition Tensor devrait probablement autoriser n'importe quelle permutation définie par l'utilisateur, avec l'inversion des dims par défaut.

La transposition Tensor devrait probablement autoriser n'importe quelle permutation définie par l'utilisateur, avec l'inversion des dims par défaut.

Cela semble compliquer un peu le type, peut-être pouvons-nous avoir un type PermuteDims qui permet une permutation de dimension paresseuse arbitraire.

@Stefan : Cela semble une très bonne idée de travailler sur le vecteur et 2-dim
algèbre. Juste quelques défis:

  1. Concernant les cas de tableau à dimensions multiples: Pour un tableau A avec dimension
    (i_1, i_2, ..., i_n), si on veut la transposition appliquée au [i_2, i_3]
    dimensions - ou, même hachage, sur les dimensions [i_2, i_4]. Pouvez-vous le faire en
    la nouvelle définition de transposer?
  2. Concernant la dimension singleton: il est possible qu'une tranche singleton soit
    laissé intentionnellement. Julia devrait-elle conserver cette dimension singleton après le
    calcul? Par exemple, si l'on définit un vecteur comme un tableau V dans le
    dimension de (2,1), et veut multiplier la transposée par une matrice A en
    dimension (2,3,4). Pouvez-vous donner le résultat de v '* A dans la dimension de
    (1,3,4)?

Le jeu 16 octobre 2014 à 14:31, Stefan Karpinski [email protected]
a écrit:

Le seul perturbateur serait en fait que M [1 ,:] est un vecteur (vertical)
plutôt qu'une matrice de lignes. Sinon, c'est en fait un assez lisse,
ensemble de changements non perturbateurs (on espère). La principale révélation (pour moi) était
que le comportement de découpage APL pourrait être combiné avec des transpositions paresseuses. Si nous obtenons
adhésion, nous pouvons élaborer un plan et répartir le travail. J'espère vraiment
que les transpositions paresseuses et les fonctions par étapes permettent une certaine réduction du code et
simplification.

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -59425385.

Re 2 & 3: après avoir eu un coup dur, je suis arrivé à la conclusion que la transposition vectorielle ne devrait PAS être un sous-type de AbstractVector , sinon tout devient compliqué (voir la discussion sur # 6837). Je pense que la manière la plus saine d'avancer est d' Transpose{T,A} <: AbstractMatrix{T} et un type Covector séparé (+ Conjugate variantes).

L'autre problème important que j'ai rencontré est que vous souhaitez souvent envoyer sur un type de matrice spécifique, sa transposition ou sa transposition conjuguée. Malheureusement, je n'ai pas pu trouver un moyen d'exprimer cela via des machines de type existantes (voir cette discussion sur la liste de diffusion ). Sans cela, je crains que nous ayons beaucoup de @eval -ing sur 3x3 combinaisons d'arguments possibles.

@simonbyrne , je m'en remets à votre expérience de la mise en œuvre. Le reste semble-t-il raisonnable?

J'ai fait remarquer (dans les forums moins publics, donc cela porte probablement une brève mention ici) qu'une alternative potentielle est de gérer toutes les formes de forme _en interne_, en développant les types d'index que les SubArrays peuvent utiliser. En particulier, on pourrait avoir un type "plage transposée" qui conférerait une forme transposée au SubArray, même lorsque le tableau parent est un Vector . (Voir https://github.com/JuliaLang/julia/blob/d4cab1dd127a6e13deae5652872365653a5f4010/base/subarray.jl#L5-L9 si vous ne savez pas comment les SubArrays sont / peuvent être implémentés.)

Je ne sais pas si cette stratégie alternative rend la vie plus facile ou plus difficile. Cela réduit le nombre de types orientés vers l'extérieur, ce qui peut signifier qu'il faut moins de méthodes. (En tant que personne qui est encore en train de remplir des méthodes manquantes en raison de la transition Color dans Images , cela semble être une bonne chose.) D'un autre côté, en l'absence de répartition triangulaire pratique, cela pourrait rendent un peu plus gênant l'écriture de méthodes sélectives, ce qui pourrait aggraver les problèmes soulevés par @simonbyrne.

Toutes les informations seraient les bienvenues.

Mis à part ces détails, j'aime la forme de la proposition de

Deux pensées:

  • Si une indexation telle que A[[2], :] devient idiomatique, il semble un peu inutile de créer un vecteur juste pour envelopper l'index unique 2 . Devrions-nous envisager d'autoriser A[(2,), :] pour la même chose, ou quelque chose de similaire? Je suppose qu'une seule plage d'élément est correcte, mais ce serait bien d'avoir une syntaxe presque aussi pratique que [2] .
  • Si nous avons besoin d'un nombre correspondant de dimensions pour faire la diffusion, il devrait y avoir un moyen simple d'ajouter des dimensions singleton à un tableau, peut-être quelque chose comme l'indexation newaxis numpy.

Je pensais proposer que l'indexation avec des points-virgules, à la A[2;:] , pourrait être un mode d'indexation différent où le résultat a toujours le même nombre de dimensions que A - c'est-à-dire ne laissez pas de singletons et indexez avec tout ce qui a plus d'un rang est une erreur. Décidé de laisser cela en dehors de la proposition de base pour la simplicité, mais quelque chose comme ça semble être une bonne chose à avoir.

Je peux voir les préoccupations exprimées par @simonbyrne . Cependant, en principe, un covecteur n'est également qu'un vecteur vivant dans un espace vectoriel différent, à savoir l'espace dual. Donc, faire du type Transpose ou Covector pas un sous-type de AbstractArray également un peu désagréable. Une résolution possible, qui constituerait un changement majeur et qui ne sera probablement pas envisagée (mais je voulais quand même le mentionner) est de donner à toute la famille AbstractArray un paramètre de type supplémentaire trans , qui pourrait avoir des valeurs :N , :T ou :C . Pour toutes les méthodes qui supposent simplement qu'un vecteur est une liste de nombres unidimensionnelle, elles n'auraient pas besoin de faire la distinction entre les différentes valeurs de ce paramètre final, et ainsi les définitions de méthode correspondantes peuvent rester telles qu'elles sont actuellement.

Pour les tableaux à N dimensions avec N> 2, il existe différentes options. Soit transpose donne une erreur et il est impossible de créer réellement un objet de type AbstractArray{3,Float64,trans}trans!=:N , ou, alternativement, :T signifie simplement ligne-majeure et transpose d'un tableau général a pour effet d'inverser toutes les dimensions. Je pense que cette dernière est également la convention acceptée par les personnes qui utilisent la notation graphique de Penrose (voir http://en.wikipedia.org/wiki/Penrose_graphical_notation bien que la transposition n'y soit pas expliquée, mais aussi voir le livre cité par Cvitanović).

Je ne vois pas vraiment le rôle des permutations d'index arbitraires pris en charge par transpose , il y a permutedims pour cela, et peut-être une approche paresseuse utilisant des SubArrays remaniés. De plus, la motivation principale de ce problème est de simplifier le zoo A_mul_B, et les contractions de tenseur d'ordre supérieur ne sont pas (et ne devraient pas être) prises en charge par une multiplication normale de toute façon.

Je suis sûr qu'il y a de nouveaux problèmes liés à cette approche auxquels je n'ai pas encore pensé.

Je pense avoir trouvé une solution raisonnable au problème d'expédition ici .

La proposition de @Jutho semble intéressante et je pense qu'elle vaut la peine d'être explorée. Malheureusement, la seule vraie façon d'évaluer ces choses est d'essayer de les mettre en œuvre.

@toivoh ,

  • A[2:2,:] conservera également la dimension, et cela ne nécessite aucune allocation ni aucune nouvelle syntaxe.
  • Quelque chose comme newaxis semble tout à fait réalisable. En effet, avec l'architecture du # 8501, il semble possible de créer une diffusion directement par indexation: avoir un type d'index qui se résout toujours à 1 quelle que soit la valeur que l'utilisateur remplit dans cet emplacement.

Le problème avec 2:2 est la répétition s'il existe une expression longue pour l'index au lieu de seulement 2 . Mais bien sûr, vous pouvez toujours définir votre propre fonction pour créer une plage à partir d'un index.

Très bonne proposition: +1 :.

Rappelez-moi pourquoi nous voulons v' == v ?

Nous n'avons pas vraiment besoin de cela mais c'est plutôt sympa puisque le dual d'un espace vectoriel (de dimension finie) lui est isomorphe.

Ou plus fortement, puisque les tableaux de Julia ne font pas la distinction entre les indices covariants et contravariants, il est logique de penser à cela comme des vecteurs dans un espace cartésien (métrique euclidienne = matrice d'identité = delta de kronecker), où en effet l'espace dual est naturellement isomorphe.

Je ne suis pas sûr que nous voulions v '== v, mais je pense que c'est assez orthogonal à
le reste. Voulons-nous une matrice de colonnes et un vecteur pour comparer égaux s'ils
ont des éléments égaux?

C'est en fait un problème différent car ils ont des nombres de dimensions différents.

En particulier, cette proposition supprime efficacement l'identification entre un vecteur et une matrice de colonne - car si vous coupez une matrice horizontalement ou verticalement, vous obtenez un vecteur dans les deux sens. Auparavant, vous pouviez en quelque sorte ignorer les dimensions de singleton de fin - ou prétendre qu'il y en avait plus qu'il n'y en avait réellement. Ce ne sera plus une bonne idée de faire cela car un vecteur peut provenir de n'importe quelle tranche d'un tableau.

Serait-il logique de convert quelque chose de 1-d à 2-d en ajoutant une dimension singleton à la fin?

Avec cette proposition, je pense que ce n'est peut-être plus une bonne idée. Mais peut-être que les vecteurs se comportent toujours comme des colonnes tandis que les covecteurs se comportent comme des lignes.

Une chose que j'ai notée dans # 8416 est que sparsevec est actuellement simulé comme une matrice CSC à une seule colonne. Sparse devrait pouvoir s'adapter assez bien à cela une fois qu'un type de vecteur fragmenté 1-d approprié est implémenté (ce qui tomberait comme le cas utile le plus simple d'un type générique Nd COO, il suffit d'écrire).

Juste en prenant tout cela. Donc, ce qui suit ne fonctionnerait pas?

A [1 ,:] * A * A [:, 1] # ligne d'une colonne Matrix * Matrix * d'une matrice ???

Tu as écrit

v'w est le produit scalaire de v et w - en particulier, c'est un vecteur scalaire, pas un vecteur à un élément.

V '* w est également un scalaire?

J'aime l'idée de dot (x, y) prenant deux éléments dont les formes sont (1, ..., 1, m, 1, ..., 1) et
retourner le produit scalaire quoi qu'il arrive. Mais je ne veux pas que x * y donne point (x, y) dans ce sens
sauf si x est un covecteur et y est un vecteur.

Je ne sais pas si c'est une idée si chaude mais peut-être que ce serait bien si
A [:, 1,1] était un vecteur et A [1,:, 1] ou A [:, 1,:] étaient des covecteurs.
Il est préférable de suivre une dimension pour le vecteur - l'emplacement sur lequel vous
sont autorisés à contracter le tenseur, l'algèbre linéaire standard étant
1 (vecteurs de ligne) et 2 vecteurs de colonne.

À mon avis, les deux principaux défis que nous avions précédemment abordés dans ce numéro étaient:

(A) comment distinguer la sémantique des tenseurs (pour les contractions) et la sémantique des tableaux (pour la diffusion) lorsque l'on travaille sur des données multidimensionnelles;
(B) comment intégrer une algèbre linéaire évidente et pratique dans un cadre cohérent qui se généralise à des dimensions supérieures.

Je ne vois pas clairement comment cette proposition traite l’une ou l’autre de ces questions. Autant que je sache, pour atteindre (A), il faut encore des manipulations ad hoc de l'utilisateur (comme avec les fonctionnalités actuelles); et adresser (B) en utilisant des wrappers paresseux nécessiterait quelque chose comme les extensions SubArray suggérées par @timholy , à quel point cela devient une version paresseuse de l'approche de masquage discutée il y a quelque temps. Je peux imaginer fournir un support supplémentaire pour (A) en utilisant un mécanisme paresseux similaire (comme un type de wrapper List ), mais dans tous ces cas, il me semble que la paresse devrait être une stratégie facultative.

Je ne sais pas combien partagent le point de vue de

Lorsque vous dites: "... ont leurs limites de conception à cet égard (comme je l'ai mentionné ci-dessus), ils sont au moins pris en charge", parlez-vous de fonctionnalités manquantes ou de quelque chose de fondamental sur les vecteurs et les transpositions qui ne peut pas être traité un niveau supérieur, ou en ajoutant des fonctions?

Est-ce que quelque chose à propos de cette proposition est en conflit avec l'amélioration de vos points (A) et (B)?

Je ne vois vraiment pas comment les contractions de tenseur sont prises en charge par l'opérateur de multiplication standard matlabs *, ou par toute autre fonction matlab intégrée pour cette question. Numpy a une fonction intégrée (j'ai oublié le nom) mais elle est également assez limitée pour autant que je me souvienne.

J'ai moi aussi besoin de contractions tensorielles dans leur forme la plus générale tout le temps, c'est exactement pourquoi je sais que spécifier la contraction tenseur la plus générale, sans parler de sa mise en œuvre efficace, n'est pas tout à fait simple. C'est pourquoi j'ai soutenu qu'il fallait des fonctions spéciales pour cela, plutôt que d'essayer d'entasser des fonctionnalités à moitié fonctionnelles ou plutôt spécifiques dans les opérateurs standard de Julia base, qui ne couvrent pas la moitié des cas d'utilisation. Mais je suis heureux de changer d'avis, par exemple s'il y a une contraction «standard» qui est tellement plus importante / utile qu'une autre? Mais cela peut être très dépendant du domaine et donc ne pas convenir en règle générale pour adoption dans Julia Base.

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

À mon avis, les deux principaux défis que nous avions précédemment abordés dans ce numéro étaient:

(A) comment distinguer la sémantique des tenseurs (pour les contractions) et la sémantique des tableaux (pour la diffusion) lorsque l'on travaille sur des données multidimensionnelles;
(B) comment intégrer une algèbre linéaire évidente et pratique dans un cadre cohérent qui se généralise à des dimensions supérieures.

Je ne vois pas clairement comment cette proposition traite l’une ou l’autre de ces questions. Autant que je sache, pour atteindre (A), il faut encore des manipulations ad hoc de l'utilisateur (comme avec les fonctionnalités actuelles); et adresser (B) en utilisant des wrappers paresseux nécessiterait quelque chose comme les extensions SubArray suggérées par @timholy , à quel point cela devient une version paresseuse de l'approche de masquage discutée il y a quelque temps. Je peux imaginer fournir un support supplémentaire pour (A) en utilisant un mécanisme paresseux similaire (comme un type de wrapper List), mais dans tous ces cas, il me semble que la paresse devrait être une stratégie facultative.

Je ne sais pas combien partagent le point de vue de

-
Répondez directement à cet e-mail ou affichez-le sur GitHub.

voici une contraction sur le dernier indice de A et le premier indice de B
un peu comme le point de Mathematica

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

J'ai toujours été capable de faire des contractions générales avec des remodelages, des permutations et peut-être des
se conjugue au besoin plus ou moins comme ci-dessus

Je ne sais pas quel est vraiment le gros problème avec les tenseurs, pourquoi ne pouvons-nous pas simplement implémenter quelques-uns de ces
les fonctions?

Oui, exactement. Dans ce numéro, tout ce que nous voulons régler pour avancer est

  1. Quelles dimensions supprimer dans l'indexation? Le "style APL" ne semble pas controversé.
  2. Que donne vector' ?

Pour les contractions tensorielles, avec des types appropriés et des fonctions par étapes, je pense que nous pourrions obtenir des implémentations assez performantes.

Mon sentiment est que les tenseurs prendront soin d'eux-mêmes et nous devons être sûrs
que les utilisateurs d'algèbre linéaire ne sont pas frustrés.

Ma plus grande préoccupation est que

(prendre une ligne d'un tableau 2d) * (tableau 2d) * (prendre une colonne d'un tableau 2d)

qui est une opération courante ne fonctionnera toujours pas à moins que nous prenions
(prendre une rangée) avec covector ou peut-être mieux encore
étiquetez-le avec un index d'emplacement général.

@JeffBezanson , quand je dis que ces opérations sont prises en charge, je veux dire que les types de données et les fonctions intégrés sont spécifiquement conçus avec eux à l'esprit, par exemple, comme la fonction Dot Mathematica. Ainsi, pour l'utilisateur, il existe un moyen intégré, documenté et / ou évident de faire certaines choses. Pour toute conception, il est possible d'obtenir un support pour n'importe quoi en ajoutant des fonctions, comme c'est le cas avec l'implémentation actuelle; ce n'est donc pas une question de conflit technique, c'est une question de conception.

@Jutho , je n'utilise pas beaucoup MATLAB, donc je ne peux pas faire de commentaire. Je suis d'accord que la conception de NumPy est moins cohérente que celle de Mathematica (comme je l'ai discuté ci-dessus), mais elle prend également en charge une gamme plus riche de comportements. Je suis d'accord que l'algèbre linéaire de base devrait laisser la machinerie tenseur générale invisible pour les utilisateurs qui n'en ont pas besoin, mais en raison des formidables fonctionnalités de langage de Julia, il ne semble pas nécessaire d'introduire des implémentations divergentes pour elles, comme NumPy et Mathematica l'ont fait. été forcé de le faire dans une certaine mesure. Il me semblait que ce problème consistait, au moins en partie, à trouver le bon système unifié pour les deux, à révéler les spécialisations à utiliser pour les cas courants d'algèbre linéaire: par exemple, que faire pour vector' .

A [1 ,:] * A * A [:, 1] # ligne d'une colonne Matrix * Matrix * d'une matrice ???

Correct - vous devrez écrire A[1,:]' * A * A[:,1] .

V '* w est également un scalaire?

Oui, v'w c'est la même chose. L'un des avantages de cette proposition est qu'elle élimine complètement les hacks syntaxiques bon marché.

Je ne sais pas si c'est une idée si chaude ...

Je ne pense pas que ce soit le cas. L'un des objectifs de cette proposition est de rendre symétriques les règles de découpage et d'indexation, ce qui rendrait l'un des indices spécial, ce qui me semble aller à l'encontre de l'objectif entier. Si le découpage doit être asymétrique, autant conserver le comportement actuel.

@thomasmcoffee Vous devrez juste être plus précis. Bien sûr, tout le monde veut que les choses soient cohérentes, documentées, évidentes, etc. La question est de savoir si la proposition sur la table sert ces objectifs? Peut-être que la proposition actuelle n'affecte pas du tout ces objectifs, ce qui est normal - alors tant qu'elle conduit à une amélioration ailleurs, nous avons encore une nette amélioration.

Alors laissez-moi clarifier les choses

Si A n'est pas carré

| | Actuel | Proposé | MATLAB |
| --- | --- | --- | --- |
| A * A [1 ,:] | Non | Oui | Non |
| A * A [1 ,:] '| Oui | Non | Oui |
| A [:, 1] A | Non |
A [:, 1] ' A | Oui | Oui | Oui |

et si A est carré

| | Actuel | Proposé | MATLAB |
| --- | --- | --- | --- |
| A * A [:, 1] | Oui | Oui | Oui |
| A * A [:, 1] '| Non | Non | Non |
| A [1 ,:] A | Non |
A [1 ,:] ' A | Non | Oui | Non |

Je jure que je viens de poster une réponse à cela, mais d'une manière ou d'une autre, elle a disparu dans l'éther. Tout cela est correct. Dans la disposition actuelle, vous devez vous demander si vous prenez une tranche de ligne ou une tranche de colonne ainsi que si vous multipliez à gauche ou à droite lorsque vous décidez de transposer ou non (les colonnes sont transposées à gauche, les lignes sont transposé à droite). Dans la proposition, vous ne considérez que de quel côté vous multipliez - vous transposez toujours à gauche, jamais à droite.

Serait-ce bien si

dot(x,y) et dot(x.',y) et dot(x,y.') et dot(x.',y.') donnent tous le même scalaire?

ie Σᵢ conj (xᵢ) * yᵢ

de cette façon on peut faire dot (x, A * y) sans trop réfléchir

Ces exemples de @alanedelman se sentent un peu transposés en arrière aux endroits où vous ne devriez pas, en raison de l'indexation APL. C'est peut-être une motivation suffisante pour avoir une variante d'indexation préservant les dimensions, comme je pense que cela a été discuté (par exemple A[i; :] ?)

Mais alors vous voudriez que cela donne un covecteur dans le cas ci-dessus.

@alanedelman , je ne vois aucune raison pour laquelle ces méthodes de dot ne devraient pas exister et donner le même résultat.

J'ai toujours été capable de faire des contractions générales avec des remodelages, des permutations et peut-être des
se conjugue au besoin plus ou moins comme ci-dessus

C'est exactement ainsi qu'il est implémenté dans la fonction tensorcontract de TensorOperations.jl, si vous choisissez la méthode: BLAS, qui est certainement la plus rapide pour les grands tenseurs. J'ai également écrit une implémentation native de julia, en utilisant la fonctionnalité Cartesian.jl (et espérons un jour en utilisant des fonctions par étapes) qui élimine le besoin de permutées (allocation de mémoire supplémentaire) et est plus rapide pour les petits tenseurs.

Je répondais juste à la fausse affirmation selon laquelle matlab fournit des fonctionnalités intégrées pour cela, ce que Julia ne fait pas. Le remodelage et les permutés sont disponibles dans Julia. Numpy a en effet la fonction tensordot qui fait exactement cela, mais ne vous permet pas de spécifier l'ordre d'index du tenseur de sortie, vous avez donc toujours besoin d'un permuté après si vous aviez un ordre de sortie spécifique en tête.

Mais cela s'éloigne trop du sujet actuel, qui est en effet d'obtenir un comportement cohérent pour l'algèbre vectorielle et matricielle.

+1 pour la proposition de Stefan. Il semble donner une sémantique extrêmement claire mais suffisamment flexible. En tant qu'utilisateur d'algèbre linéaire, même habitué à la syntaxe de style MATLAB, je trouverais les règles assez simples à utiliser.

Je suis un peu confus sur ce que signifie ' en général. Si v est un vecteur, v' est un transpose . Si a est un tableau 2d, a' serait maintenant aussi un transpose . Ces deux semblent être définis dans l'intérêt de pouvoir former facilement b' * a en contractant la première dimension de b avec la première dimension de a .

Il ne semble pas y avoir de consensus sur une définition de a' lorsque la dimension de a est> 2. Je n'ai entendu personne proposer autre chose que d'inverser les dimensions, et cela coïncide avec b' * a contractant la première dimension de b avec la première dimension de a .

Je pense que ce serait bien si nous pouvions énoncer succinctement ce que fait ' sans faire référence à la taille de l'objet sur lequel il opère.

Il semble raisonnable d'avoir une autre fonction de contraction disponible dans Base pour des situations plus générales, par exemple contract(a, b, 2, 3) pour contracter la 2e dimension de a avec la 3e de b .

À propos, dot(a,b) == a'*b quand a et b sont des vecteurs, mais dot(a,b) n'est actuellement pas défini pour les matrices. Pouvons-nous avoir dot(a,b) = trace(a'*b) ?

@madeleineudell : Je suis un peu confus sur ce que «est censé vouloir dire en général.

Je partage cette inquiétude. Vous pouvez essentiellement prendre les propriétés 2-5, 7, 8 et 10 dans ma proposition comme caractéristiques déterminantes. Ie vous voulez que ceci tienne:

  • v' est unidimensionnel mais pas un vecteur
  • v'' === v
  • v' == v
  • v'w est un scalaire
  • v*w' est une matrice
  • v'*M est le même genre de chose que v'
  • M' est bidimensionnel mais pas une matrice, peut-être une vue matricielle

En particulier, il n'y a aucune contrainte sur ce que cela signifie ou fait pour les tableaux de dimensions supérieures. Une théorie générale de ce que sont les covecteurs qui englobent des dimensionnalités plus élevées serait bien, mais je ne suis pas convaincu que cela puisse vraiment être fait sans trop compliquer les choses, par exemple en ayant des dimensions haut / bas ou en ayant chaque dimension étiquetée par un index.

Il est clair au moins que la règle générale pour ' n'est pas que cela inverserait les dimensions, puisque ce n'est pas ce que cela ferait pour les vecteurs et les covecteurs.

La seule règle simple à laquelle je puisse penser qui capture le comportement ci-dessus pour les vecteurs, les covecteurs et les matrices est de permuter les deux premières dimensions (un vecteur a une seconde dimension absente et un covecteur a une première absente et une seconde présente).

Le 20 octobre 2014, à 09h05, toivoh [email protected] a écrit:

Il est clair au moins que la règle générale pour 'n'est pas que cela inverserait les dimensions, puisque ce n'est pas ce qu'il ferait pour les vecteurs et les covecteurs.

La seule règle simple à laquelle je puisse penser qui capture le comportement ci-dessus pour les vecteurs, les covecteurs et les matrices est de permuter les deux premières dimensions (un vecteur a une seconde dimension absente et un covecteur a une première absente et une seconde présente).

Je dirais que ce n'est pas vrai si vous voulez penser aux tenseurs généraux. Cela pourrait être vrai si vous ne considérez que les matrices et les vecteurs comme des blocs contenant des nombres, et que vous considérez v comme une colonne et v 'comme une ligne.

Cependant, si vous considérez v comme un élément d'un espace vectoriel, et w = v 'comme une manière de mapper v à un élément w de l'espace dual V_, alors w est toujours un vecteur, c'est-à-dire un objet unidimensionnel. En général, il faut une métrique pour définir cette correspondance de V à V_, c'est à dire w_i = g_ {i, j} v ^ j. Or si V est un espace cartésien, c'est-à-dire R ^ n avec la métrique euclidienne standard, alors V * est naturellement isomorphe à V. Cela signifie qu'il n'y a pas de notion d'indices supérieur ou inférieur (covariant ou contravariant) et donc que w_i = v_i = v ^ i = w ^ i. Je dirais que c'est le cas utilisé dans la plupart des programmes, c'est-à-dire le cas qui doit être pris en charge par Julia Base. (Pour un espace vectoriel complexe, V * est naturellement isomorphe à Vbar, l'espace vectoriel conjugué, et donc l'application naturelle est w_i = conj (v ^ i).)

La convention pour désigner les vecteurs comme des colonnes avec des nombres, des vecteurs doubles comme des lignes avec des nombres, et donc des matrices (qui sont des éléments de V \ otimes V_) comme des blocs avec des nombres est extrêmement pratique, mais s'arrête dès que vous voulez également considérer des dimensions supérieures tableaux, c'est-à-dire des éléments d'espaces de produits tensoriels d'ordre supérieur. Dans ce cas, un 'vecteur ligne', c'est-à-dire un objet bidimensionnel où la première dimension a la taille 1, pour le dire dans la terminologie matlab / julia, est un élément d'un espace de produit tensoriel V1 \ otimes V2, où V1 arrive juste à être R (ou un autre espace unidimensionnel). Je conviens que ce n'est peut-être pas ce que vous voulez comme comportement par défaut, mais je préférerais que l'on ne devrait tout simplement pas essayer de définir la transposition pour un tableau général et de faire référence aux permutés, comme le fait matlab. Inverser les deux premières dimensions d'un tenseur général comme convention par défaut n'a pas de sens. La transposition d'un élément à partir d'un espace produit tensoriel d'ordre supérieur V1 \ otimes V2 \ otimes… \ otimes Vn n'a pas de définition unique. La convention d'inverser toutes les dimensions découle simplement d'une représentation graphique pratique de Penrose, comme mentionné ci-dessus. De plus, c'est celui qui mappe l'espace matriciel (V \ otimes V_) à lui-même (V * \ otimes V * = V \ otimes V ).

Je peux voir deux voies à suivre:
1) Faire fonctionner l'opérateur de convenance (et peut-être même *) avec des tableaux arbitraires d'ordre supérieur en utilisant une convention choisie, dans le cadre des tenseurs cartésiens (c'est-à-dire pas de distinction entre les indices supérieurs ou inférieurs). Cela pourrait apporter des résultats surprenants pour des cas d'utilisation typiques.

2) Restreindre 'et * aux vecteurs et aux matrices. Erreur sur les tableaux d'ordre supérieur. Je pense que c'est l'approche la plus populaire (par exemple Matlab, etc.).

Ce post est un peu une prise de l'autre côté de la position que j'ai prise l'année dernière, afin de

explorez l'ambivalence des deux côtés.

J'espère que ça va.
J'ai finalement compris qu'il y avait une logique dans l'approche actuelle, mais elle n'a jamais été articulée.
Cela ressemblait tellement à un hack que cela m'ennuyait. D'une certaine manière maintenant que je comprends
ça, je l'aime plus.

Dans l'approche actuelle, tous les tableaux sont de dimension infinie avec des 1 abandonnés implicites.
L'opérateur apostrophe 'aurait pu signifier échanger les deux premières dimensions,
mais en fait tel qu'il existe aujourd'hui, cela signifie

ndim (A) <= 2? interchange_first_two_dims: no_op

désolé si tout le monde a vu ça et je l'ai raté. Mon esprit était embourbé
avec l'idée que les vecteurs étaient unidimensionnels et non infinis, et
donc une transposée doit inverser les dimensions, donc être un no_op.

Je suis d'accord avec l'apostrophe qui fait ça, ou l'apostrophe toujours interchangeable
les deux premières dimensions - je ne pense pas que je m'en soucie. Je pense que l'apostrophe existe
pour l'algèbre linéaire et non l'algèbre multilinéaire.

Je me suis demandé si apostrophe-star ("'*") (si possible! Ou autre chose si impossible)
devrait signifier le contrat de la dernière dimension à la première dimension (comme le point de Mathematica)
et ont une indexation apl et aucun covector. Une partie de mon cerveau pense toujours qu'elle vaut la peine d'être explorée
mais à mesure que je me réveille, l'approche actuelle semble de mieux en mieux.
(Voyons ce que je ressens plus tard aujourd'hui)

Je ne regrette pas d'avoir commencé ce fil l'année dernière, mais je me demande encore dans quels cas
vraiment ennuyer les gens aujourd'hui ... pouvons-nous obtenir une liste d'exemples? ... et si
faire la lumière sur ces cas vaut mieux que de changer ce que nous faisons.

J'ai lu tout ce fil et j'ai vu de nombreux principes - ce qui est bien,
mais pas assez de cas d'utilisation pour mettre ma tête autour des choses.

Je peux dire que j'ai souvent été ennuyé par

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

principalement parce que je n'arrive pas à m'en souvenir.

Juste une expérience de pensée. Quelle fonctionnalité serait perdue si nous redéfinissions * pour servir d'opérateur de contraction similaire à ce que @alanedelman a en tête pour '* ? Cela ne supprimerait-il pas du tout le besoin de ' dans les opérations algébriques linéaires (à part fonctionner comme une transposition pour les matrices)? Je peux seulement voir que cela nous priverait du produit externe w*v' , qui pourrait facilement être remplacé par outer(w,v) .

EDIT: En supposant que le découpage APL soit. Par exemple A[1,:]*A*A[:,1] aurait le résultat attendu sans qu'il soit nécessaire de transposer le premier opérande avec ' .

J'y ai pensé aussi. Je pense que v * w étant un produit scalaire semble être une surcharge de stéroïdes
cela peut être sujet aux erreurs.
C'est un endroit où le point de Mathematica ne semble pas si mal.

Donc, pour résumer, un contrat du dernier au premier semble raisonnable, mais
si cela pourrait être une étoile apostrophe ou pourrait être * ou devrait être un point
a des problèmes.

Quelque peu sans rapport mais pas complètement sans rapport ...
Je tiens à souligner que l'étoile de point que tout le monde lit à l'origine comme une étoile de point (on me le dit) était
censé être une étoile car c'est l'opérateur POINTWISE
était destiné

Je peux dire que j'ai souvent été ennuyé par

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

principalement parce que je n'arrive pas à m'en souvenir.

J'ai toujours pensé "pourquoi n'utilisons-nous pas seulement , ?"

Quelle fonctionnalité serait perdue si nous redéfinissions * pour servir d'opérateur de contraction similaire à ce que @alanedelman a en tête pour '*?

Nous perdrions l'associativité: par exemple, (M*v)*v donnerait le courant dot(v,M*v) (un scalaire), tandis que M*(v*v) donnerait M.*dot(v,v) (une matrice).

Pourquoi ne faisons-nous pas simplement dot l'opérateur de contraction (qui n'est pas associatif de toute façon)? Nous pourrions également définir des contractions d'ordre supérieur, par exemple ddot(A::Matrix,B::Matrix) == A⋅⋅B == trace(A'*B) .

Alors, est-ce que je comprends bien que le seul but de l'introduction de transpositions vectorielles est de nous sauver de la non-associativité de * ? L'ambiguïté de l'exemple de @alanedelman pourrait être corrigée en transposant l'un des vecteurs ( M*v*v' vs M*v'*v ) mais la même chose peut être faite avec des parenthèses ( M*(v*v) vs (M*v)*v ) sans tous les tracas de l'implémentation de la transposition pour les vecteurs (comme @Jutho l'a déjà souligné, l'implémentation de la transposition pour les tenseurs d'ordre supérieur n'a de toute façon aucun sens mathématiquement). Pour moi, la question est de savoir quelle notation est la plus lisible / concise / élégante / pure.

En fait, M*(v*v) et (M*v)*v sont actuellement tous deux analysés comme

*(M, v, v)

pour permettre un produit matriciel qui optimise l'ordre de multiplication en fonction de la taille des arguments, il faudrait donc également modifier l'analyseur.

Mais au moins personnellement, je pense que l'associativité est un gros problème. Vous devez traduire entre les mathématiques et la plupart des langages de programmation; pour Julia, j'espère toujours que vous n'aurez pas beaucoup à traduire.

(comme @Jutho l'a déjà souligné, implémenter la transposition pour les tenseurs d'ordre supérieur n'a de toute façon aucun sens mathématiquement).

Je ne pense pas que ce soit exactement ce que j'ai dit.

Quelle fonctionnalité serait perdue si nous redéfinissions * pour servir d'opérateur de contraction similaire à ce que @alanedelman a en tête pour '*?

On perdrait l'associativité: par exemple (M_v) _v donnerait le point courant (v, M_v) (un scalaire), alors que M_ (v_v) donnerait M._dot (v, v) (une matrice).

Pourquoi ne pas simplement faire dot l'opérateur de contraction (qui n'est de toute façon pas associatif)? Nous pourrions également définir des contractions d'ordre supérieur, par exemple ddot (A :: Matrix, B :: Matrix) == A⋅⋅B == trace (A '* B).

Avec cette ligne de raisonnement, nous n'avons plus besoin de l'opérateur de multiplication pour les vecteurs et les matrices, nous pouvons simplement tout écrire en termes de point:
A ∙ B
A ∙ v
v ∙ A ∙ w
v ∙ w

C'est donc juste la proposition @pwl mais avec * remplacé par dot.

Mais au moins personnellement, je pense que l'associativité est un gros problème. Vous devez traduire entre les mathématiques et la plupart des langages de programmation; pour Julia, j'espère toujours que vous n'aurez pas beaucoup à traduire.

Je suppose qu'une partie du problème est que même en mathématiques, il existe différentes conventions. Pensons en termes de mathématiques de vecteurs de lignes et de colonnes, ou voulons-nous le comportement plus abstrait en termes d'opérateurs linéaires et d'espaces doubles (où je suppose qu'un opérateur n'est pas multiplié par un vecteur, mais appliqué à un vecteur, alors pourquoi ne pas écrire A (v) au lieu de A * v ou A ∙ v)?

Je n'ai pas grand besoin de syntaxe de commodité, je préfère personnellement écrire point (v, w) sur v '* w à chaque fois, car le premier exprime plus clairement l'opération mathématique indépendante de la base (en fait, il faut d'abord définir un produit scalaire avant même qu'une cartographie naturelle des vecteurs aux vecteurs doubles puisse être définie). =

Désolé d'être si ignorant, mais pourquoi exactement M[1, :] ne peut-il pas être une transposition se comportant comme v' ?

Je vais ajouter à ma liste de choses qui me dérangent

1.

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

2.
v=rand(3)
v' * v a actuellement ndims == 1 (la proposition de @StefanKarpinski corrige ce problème)
(v' * v)/( v' * v ) a ndims ==2 (cela me dérange vraiment vraiment et serait également corrigé)

Pourtant je ne veux pas vraiment de covecteurs
et l'indexation apl est quelque chose sur lequel je continue à aller et venir
--- je pense toujours, mais j'aimerais voir la liste des choses qui
bug d'autres personnes dans julia actuelle

J'aime vraiment l'idée de cdotattribution du dernier index au premier index en plus de l'opérateur *
et autoriser le point (a, b, ..) permet des contrations très générales.

Malgré la convention de dénomination de Numpy (et peut-être l'apparence de mon article précédent), j'ai des sentiments quelque peu mitigés sur l'utilisation de dot pour les contractions générales du tenseur. Voici la première phrase de Wikipédia:

En mathématiques, le produit scalaire, ou produit scalaire (ou parfois produit interne dans le contexte de l'espace euclidien), est une opération algébrique qui prend deux séquences de nombres de même longueur (généralement des vecteurs de coordonnées) et renvoie un seul nombre.

Dans mon esprit aussi, dot devrait renvoyer un scalaire.

@ brk00 , le problème avec votre question est qu'il n'est pas clair comment étendre cela à des tranches de tableaux de dimension supérieure / de rang supérieur (je n'aime vraiment pas la dimension mondiale pour cela).

Désolé d'être si ignorant, mais pourquoi exactement M [1,:] ne peut-il pas être une transposition, se comportant comme v '?

C'est possible, mais comment généraliser cela?

@Jutho , je suis désolé, j'aurais dû le formuler différemment. Je voulais dire que votre ligne "Transposer un élément à partir d'un espace de produit tensoriel d'ordre supérieur [...] n'a pas de définition unique", il n'y a donc pas de motivation mathématique pour introduire une définition particulière, ou pour la définir du tout.

@alanedelman :

Je vais ajouter à ma liste de choses qui me dérangent

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

Le comportement de concaténation n'est pas lié à ce problème. Ne brouillons pas davantage les eaux.

J'aime vraiment l'idée de `cdot contracter le dernier index au premier index en plus de l'opérateur *
et autoriser le point (a, b, ..) permet des contrations très générales.

C'est aussi tangentiel. Restons concentrés sur les tableaux et le découpage.


Dans l'approche actuelle, tous les tableaux sont de dimension infinie avec des 1 abandonnés implicites.

Ce n'est pas vraiment vrai. Si c'était vrai, il n'y aurait aucune distinction entre ones(n) , ones(n,1) , ones(n,1,1) , etc. Mais ce sont tous des objets différents avec des types différents qui ne sont même pas égaux à chacun autre. Nous faisons un certain effort pour implémenter les choses afin qu'elles se comportent de manière similaire, mais c'est loin d'être des tableaux de dimension infinie.


La liste des choses qui sont actuellement gênantes reflète en grande partie (un sous-ensemble de) les belles propriétés de ma proposition ci-dessus. À savoir:

  1. comportement de tranchage asymétrique - les dimensions de fin sont spéciales.
  2. v'' !== v - en fait v'' != v ; c'est parce qu'ils n'ont pas le même rang.
  3. v' != v - même offre.
  4. v'w est un vecteur à un élément, pas un scalaire.
  5. nous avons besoin d'une analyse spéciale pour A*_mul_B* , ce qui est la chose la plus horrible qui soit.

Je suis d'accord avec la plupart des points de v' == v : je ne pense pas qu'ils devraient être égaux. Oui, ils peuvent être isomorphes mais ce sont toujours des objets différents, en ce sens qu'ils se comportent très différemment: les matrices M et M' sont également isomorphes mais je ne pense pas que nous voudrions jamais qu'elles soient égales (à moins qu'ils ne soient hermitiens bien sûr).

Mon point de vue général avec les types Covector était qu'ils devaient être relativement légers: outre les opérations de base (multiplication, addition, etc.), on éviterait de définir trop d'opérations (j'hésiterais même à définir l'indexation ). Si vous voulez en faire quelque chose, vous devez le reconvertir en vecteur et y faire votre travail.

+1 à la prise de point de vue de

Aussi d'accord avec @simonbyrne.

Oui, v et v' ont des types différents, mais nous considérons déjà que différents types de tableaux avec la même forme et les mêmes données sont égaux - par exemple speye(5) == eye(5) . Pourquoi covector est-il différent de clairsemé?

J'espère que les covectors diffuseront comme des matrices de lignes. Pour les tableaux A et B considérés comme égaux, il en est jusqu'à présent

all(A .== B)

ce qui ne serait pas le cas si l'un est un vecteur et l'autre un covecteur.

Pour moi, un covecteur ressemble plus à une matrice de lignes qu'à une matrice de vecteurs ou de colonnes.

Je suppose qu'une question clé est: qu'est-ce que size(v' )? Si la réponse est (length(v),) alors je pense que v == v' devrait être vrai. Si size(v') == (1,length(v)) alors cela devrait probablement être faux, mais on peut soutenir que v' == reshape(v,1,length(v)) devrait être vrai. La question est donc de savoir si v' être un type particulier de vecteur ou un type particulier de matrice?

De plus en plus, je suis convaincu que cette question concerne vraiment ce que signifie réellement transposer.

Les covecteurs ne sont pas vraiment des tableaux, et je ne pense donc pas que nous puissions vraiment dire quelle "forme" ils ont. Un covecteur est vraiment défini par ce qu'il fait, c'est-à-dire que *(::Covector, ::Vector) donne un scalaire: comme tout AbstractVector ne fait pas cela, ce n'est pas vraiment la même chose.

Un autre problème serait dans le domaine complexe: aurions-nous v' == v ou v.' == v ?

@simonbyrne :

Un covecteur est vraiment défini par ce qu'il fait, c'est-à-dire que *(::Covector, ::Vector) donne un scalaire: comme tout AbstractVector ne fait pas cela, ce n'est pas vraiment la même chose.

C'est un très bon point. Cependant, cela peut être assez frustrant si v' ne peut pas être utilisé comme un objet. Peut-être que la bonne approche est de traiter v' comme une matrice de lignes amusante au lieu d'un vecteur amusant.

Peut-être que la bonne approche est de traiter v 'comme une matrice de lignes amusante au lieu d'un vecteur amusant.

En quelque sorte, mais je ne pense pas que ce devrait être un sous-type de AbstractMatrix non plus: je pense que AbstractCovector devrait être un type de premier niveau. Je serais heureux de définir length(::Covector) , mais je ne pense pas que nous devrions définir une méthode size .

Je ne suis pas sûr de la gestion de la diffusion: à moins que nous puissions trouver des critères raisonnables, j'aurais tort de ne pas définir les méthodes de diffusion.

Cette discussion semble converger vers l'utilisation de transposés et de vecteurs tels qu'ils sont utilisés en ingénierie, c'est-à-dire penser à tout comme étant des matrices. Pensez aux vecteurs comme une colonne et aux convecteurs comme une ligne. C'est bon pour les vecteurs et les cartes linéaires dans l'espace cartésien (le cas d'utilisation typique), mais commence à échouer si l'on essaie de généraliser à l'algèbre multilinéaire ou à des espaces vectoriels plus généraux etc. Il semble y avoir beaucoup de confusions provenant du fait que espaces cartésiens beaucoup de choses sont équivalentes qui ne sont pas équivalentes pour les espaces généraux. Je ne suis pas nécessairement opposé à cela en tant que comportement par défaut de Julia, mais je suis vraiment en désaccord avec certaines des affirmations ci-dessus comme si elles seraient «mathématiquement correctes». Alors permettez-moi de contredire ceux ci-dessous.

Le 20 octobre 2014, à 17h39, Simon Byrne [email protected] a écrit:

Je suis d'accord avec la plupart des points de

Cette déclaration n'a aucun sens à aucun niveau.

1), je pense que vous abusez du sens d'isomorphe ici. Isomorphe est une relation entre deux espaces (dans ce cadre). En général, pour tout espace vectoriel (réel) V, il existe un espace dual V * de fonctions linéaires (pour les espaces complexes, il y a aussi l'espace conjugué et le double de l'espace conjugué). Ce sont typiquement des espaces différents, et il n'y a même pas de cartographie unique ou naturelle de l'un à l'autre, c'est-à-dire qu'il n'y a en général aucun sens d'associer les éléments v de V aux éléments phi de V *. La seule opération générale est l'application de la fonction linéaire, c'est-à-dire que phi (v) est un scalaire.

Une cartographie naturelle de V à V * peut être définie une fois que vous avez une forme bilinéaire V x V -> scalaires, généralement un produit / métrique interne. On peut alors définir phi_i = g_ {i, j} v ^ j.

2), il n'y a en fait aucune opération naturelle associée à la "transposition d'un vecteur" (voir point 3). Cette confusion provient de l'identification des vecteurs avec des matrices de colonnes.
En le mettant probablement trop fort, j'aimerais en fait voir un cas d'utilisation de la transposition d'un vecteur que vous ne pouvez pas obtenir en utilisant un point et peut-être une opération de produit externe / produit tensoriel?

Cependant, si vous voulez utiliser la transposition comme moyen de mapper des vecteurs sur des vecteurs doubles, c'est-à-dire de mapper v dans V à un certain phi = v 'dans V_, alors vous supposez que vous travaillez avec le produit interne euclidien standard (g_ { i, j} = delta_ {i, j}). À ce stade, vous éradiquez la distinction entre les indices covariants et contravariants, vous travaillez essentiellement avec des tenseurs cartésiens, et V et V_ deviennent naturellement isomorphes. Comme indiqué ci-dessus, w_i = v ^ i = v_i = w ^ i, donc oui, v == w et je dirais même qu'il n'y a rien qui distingue ces deux.

3) L'opération «transposer» n'est à l'origine définie que pour les cartes linéaires de V -> W (http://en.wikipedia.org/wiki/Dual_space#Transpose_of_a_linear_map), et en fait, même là, cela pourrait ne pas signifier ce que vous pensez. La transposée d'une application linéaire A: V-> W est une application A ^ T de W _-> V_, c'est-à-dire qu'elle agit sur des vecteurs dans W_, sur des vecteurs doubles, et produit des éléments de V_, c'est-à-dire des covecteurs de V. Cela signifie, si l'on y pense en termes de transposée habituelle A ^ T d'une matrice A, que cette matrice A ^ T est à multiplier par des colonnes représentant des vecteurs dans W * et que la colonne qui en sort représente un covecteur de V Ainsi, à ce stade, l'identification des vecteurs doubles avec des vecteurs lignes échoue déjà.

Cependant, dans le cas d'utilisation typique d'espaces vectoriels réels où V * et W * sont identifiés à V et W via le produit interne euclidien standard, la transposée d'une carte linéaire peut être identifiée avec l'adjoint de cette carte linéaire, qui est en effet une carte de V-> W comme la plupart des gens le pensent est également le cas pour la transposition de la carte. Dans le cas complexe, cela échoue car la transposition matricielle ordinaire (sans conjugaison complexe) n'a de sens que comme application W _-> V_, en tant qu'application W-> V, ce n'est pas une définition indépendante de la base et donc pas opérationnellement significative.

Quant à ce que la transposition devrait signifier pour les tableaux de dimensions supérieures, dans l'approche matlab / ingénierie vers laquelle nous convergeons: cela devrait être une erreur, cela ne se généralise pas. Cela ne veut pas dire qu'il n'y a aucun moyen de le définir. Le problème est de savoir que représente un tableau d'ordre supérieur. Est-ce un élément d'un espace de produits tensoriels V1 \ otimes V2 \ otimes… \ otimes VN, est-ce une application multilinéaire agissant sur V1 x V2 x… x VN, est-ce une application linéaire à partir d'un certain espace de produits tensoriels V1 \ otimes V2 \ otimes … \ Otimes VN à un autre espace produit tensoriel W1 \ otimes W2 \ otimes… \ otimes WM? Pour le cas bidimensionnel, il existe une résolution. Identifier les applications linéaires A: V-> W avec des vecteurs dans W \ otimes V_, la transposition au sens de l'application linéaire correspond à l'inversion des espaces dans cet espace de produit tensoriel, A ^ i_j -> A_j ^ i avec A ^ T dans V_ \ otimes W = V * \ otimes W _, qui est bien une carte de W_ -> V. La bonne chose à propos de l'inversion des dimensions pour les objets d'ordre supérieur est qu'elle a une représentation graphique pratique.

En conclusion, je pense que le problème est de savoir si le vecteur de Julia a besoin de capturer les propriétés d'un vecteur général arbitraire au sens mathématique, ou s'il ne représente qu'une colonne de nombres, une liste,… Les mots vecteur, tenseur,… ont bien -défini des significations opérationnelles et indépendantes de la base en mathématiques, qui peuvent entrer en conflit avec le type d'opérations que vous souhaitez définir sur un vecteur Julia. À l'inverse, certains objets sont en réalité des vecteurs du point de vue mathématique (vecteurs doubles, etc.) qui ne devraient probablement pas être identifiés avec Julia Vectors, car le type standard Julia (Abstract) Vector n'a aucun moyen de distinguer les différents espaces vectoriels.

À cet égard, il y a une certaine asymétrie, puisque le terme Matrix est beaucoup moins surchargé, même du point de vue mathématique, une matrice n'est qu'une représentation pratique d'une carte linéaire sur une certaine base. Notez qu'il y a aussi de nombreux cas où vous ne voulez pas représenter une carte linéaire comme une matrice, mais plutôt comme une fonction (ce problème a déjà été soulevé par exemple dans les arguments de eigs, etc.). À cet égard, je peux comprendre pourquoi matlab ne s'est même pas donné la peine d'avoir un vecteur, une structure vraiment unidimensionnelle. Dans cette approche, tout est simplement interprété comme une «matrice», c'est-à-dire un bloc avec des nombres, de toute façon.

Question? Soit c un covecteur. Qu'est-ce que fft (c)?

Réponse: fft (c ')' qui prend des conjugués complexes de manière potentiellement inattendue
par rapport à fft (c)

les utilisateurs pourraient tout simplement bénéficier du fait qu'il ne soit pas défini
(ou peut-être que ce serait bon pour les utilisateurs, si bien documenté ??)

Je parie qu'il y a beaucoup plus de ce genre de choses

Je soupçonne que la bonne chose à faire dans Base pour l'instant est de définir uniquement:

  • covector fois un autre vecteur
  • covecteur multiplié par une matrice
  • covector 'pour obtenir un vecteur

+1 au support minimal pour les covecteurs pour le moment.

Oui, +1.

Donc si j'ai mentionné que je suis presque sûr que
norme (covecteur, q) devrait être norme (vecteur, p) où 1 / p + 1 / q = 1
de l'inégalité Holder, cela ne serait pas mis en œuvre avant longtemps. :-)

Dieu merci pour p = q = 2

Ce ne serait pas si difficile à mettre en œuvre:

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

Vous voudriez probablement une vérification supplémentaire pour éviter q == 0 et q == 1 .

Je pense que q == 1 serait bien

Med venlig hilsen

Andreas Noack

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

Ce ne serait pas si difficile à mettre en œuvre:

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

Vous voudriez probablement quelques vérifications supplémentaires pour éviter q == 0 et q == 1.

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -60139762.

Je travaille sur une rédaction de la proposition de covector (que je soutiens principalement), mais il peut être utile de poster maintenant un point qui précise la notion de @StefanKarpinski de "matrice de lignes amusantes".

Les covecteurs ne sont pas des vecteurs, mais la description du pourquoi n'est pas toujours claire. Un covecteur (ou bra-vecteur, comme l'appelleraient les physiciens) est une fonctionnelle linéaire qui mange un vecteur et crache un nombre qui est un produit scalaire.

Plus précisément:

  • Soit V = V(F) et W = W(F) un vecteur sur un champ d'éléments F ,
  • v et w sont des vecteurs qui sont respectivement des éléments de V et W ,
  • <.,.> être un produit interne tel que <.,.> : V × W → F et v, w ↦ <v, w>

Le covecteur correspondant à v est la fonctionnelle linéaire v' : W → F qui effectue le mappage w ↦ <v, w> .

La description habituelle se termine en disant que v' est un élément d'un double espace V* avec pas grand chose d'autre dit sur ce que le double espace _ est_.

Pour les informaticiens, il existe une description simple de v' comme un curry du produit interne par rapport à son premier paramètre, et V* comme la collection de fonctions qui sont à un paramètre currys avec différents premiers vecteurs.

Cet article Wikipédia peut être utile pour réconcilier les différentes notations associées aux deux descriptions, mais elles expriment exactement le même concept.

De plus, j'ai d'abord pensé que l'indexation dans un covector devait être interdite. Cependant, indexer v'[1] équivaut à appliquer v' au vecteur de base canonique e₁ = (1, 0, ...) , de sorte que v'[1] peut être défini comme <v, e₁> . Une sémantique d'indexation plus générale comme 1:n découle naturellement de l'application de v' à une matrice de projection appropriée, chaque colonne étant un vecteur de base canonique.

Considérer la sémantique d'indexation des covecteurs dans le contexte de # 987 donne une autre raison pour laquelle les covecteurs ne sont pas AbstractVector s: il n'est pas possible en général d'indexer v' moindre coût car tout dépend du coût des quantités comme <v, e₁> sont à calculer. En général, vous pourriez avoir un produit interne défini par rapport à une matrice <v, w> = v'*A*w et le coût de l'indexation est dominé par le produit matvec A*w (ou A'*v ), ce qui est certainement trop cher pour être considéré comme un AbstractVector .

Puisque nous parlons de DFT et de normes ... cela m'a traversé l'esprit aujourd'hui

si f est une fonction d'un vecteur et produit un scalaire, alors je voudrais que diff(f) soit une fonction qui accepte un Vector et produit un Covector de sorte que f(x) ~= f(x0) + diff(f)(x0) * (x-x0) . Une utilisation très courante du gradient consiste à obtenir une approximation linéaire du changement incrémentiel dans la sortie de la fonction étant donné un changement incrémental dans l'entrée. Si l'entrée est un vecteur, alors il semble naturel que le gradient soit quelque chose qui se contracte le long de toutes les dimensions de l'entrée.

Mais si je devais mettre en œuvre la descente de gradient, je devrais ajouter (une version à l'échelle de) le gradient à x à lui-même. En ce sens, le gradient est un vecteur qui pointe dans la direction la plus raide, dont la norme est proportionnelle au taux de changement le long de cette direction la plus abrupte.

Mon instinct dit que "le gradient de la fonction d'entrée vectorielle en un point est un covecteur" est plus important.

Je soupçonne que la bonne chose à faire dans Base pour le moment est de définir uniquement:

covector fois un autre vecteur
covecteur multiplié par une matrice
covector 'pour obtenir un vecteur

Malgré l'impression que vous avez pu avoir de mes précédents articles terribles, +1 à cela.

Néanmoins, quelques remarques à ce sujet:

Je travaille sur une rédaction de la proposition de covector (que je soutiens principalement), mais il peut être utile de poster maintenant un point qui précise la notion de @StefanKarpinski de "matrice de lignes amusantes".

Les covecteurs ne sont pas des vecteurs, mais la description du pourquoi n'est pas toujours claire. Un covecteur (ou bra-vecteur, comme l'appelleraient les physiciens) est une fonctionnelle linéaire qui mange un vecteur et crache un nombre qui est un produit scalaire.

Je pense que les vecteurs / covecteurs doubles (je préfère aussi le terme de fonctionnelles linéaires) peuvent être définis sans avoir de produit interne. C'est juste que vous avez besoin d'un produit interne si vous souhaitez définir un mappage de V à V *

Plus précisément:

Soit V = V (F) et W = W (F) vecteur sur un certain champ d'éléments F,
v et w sont des vecteurs qui sont respectivement des éléments de V et W,
<.,.> être un produit interne tel que <.,.>: V × W → F et v, w ↦
Il est assez étrange, et je pense impossible, d'avoir un produit interne défini entre deux espaces vectoriels V et W. Comment définissez-vous la propriété de la définition positive?
Le covecteur correspondant à v est la fonctionnelle linéaire v ': W → F qui effectue l'application w ↦.

La description habituelle se termine en disant que v 'est un élément d'un espace dual V * sans grand chose d'autre à dire sur ce qu'est l'espace dual.

Je pense qu'il est assez clair, certainement pour le cas des dimensions finies, que l'espace dual, par son nom même, est un espace vectoriel. Cependant, le résumé de mon discours précédent est que le type de vecteur (abstrait) de Julia ressemble plus à une liste. Il peut être utilisé pour représenter certains vecteurs, et d'autres structures de données unidimensionnelles qui n'ont pas la structure mathématique d'un vecteur, mais il n'est pas suffisamment général pour capturer tous les objets qui sont des vecteurs mathématiques (car il ne peut pas faire la distinction entre différents vecteurs les espaces).

Considérer la sémantique d'indexation des covecteurs dans le contexte de # 987 donne une autre raison pour laquelle les covectors ne sont pas des AbstractVectors: il n'est pas possible en général d'indexer v 'à bon marché car tout dépend du coût de quantités comme= v'_A_w et le coût d'indexation est dominé par le produit matvec A_w (ou A'_v), qui est certainement trop cher pour être qualifié de AbstractVector.

C'est une sorte d'argument de boucle. Comme mentionné ci-dessus, une fonctionnelle linéaire (covecteur) est plus générale et existe même pour les espaces vectoriels sans produit interne. Deuxièmement, lorsque la métrique est une matrice définie positive A, l'application naturelle d'un vecteur v à un covecteur est en effet v'_A. Cependant, quel est ce v 'ici? Est-ce déjà un mappage de v à un covecteur différent défini par rapport à la norme euclidienne standard (avec une identité comme métrique)? Non, ce n'est pas le cas. Si les vecteurs ont des indices contravariants (supérieurs) et que les covariables ont des indices covariants (inférieurs), alors la métrique A a deux indices inférieurs et le produit interne est= v ^ i A_ {i, j} v ^ j. Et donc ce mappage peut être écrit comme (avec phi le covecteur associé au vecteur v) comme phi_j = v ^ i A_ {i, j}. (Notez que ceci est différent d'un opérateur linéaire typique, qui est de la forme w ^ i = O ^ i_j v ^ j). Par conséquent, le v dans cette expression v'_A est toujours celui avec un indice supérieur, c'est toujours le même vecteur.

Donc en fait, l'un des points que j'essayais de souligner ci-dessus, c'est que les gens écrivent souvent v 'quand ils n'essaient pas vraiment de travailler avec un covecteur. Ils essaient juste d'écrire des expressions qui sont définies sur des vecteurs, comme un produit interneou quelque chose comme v ^ i A_ {i, j} w ^ j dans la représentation matricielle familière comme v'_A_w. Même le produit interne euclidien standard v'w doit être lu comme v ^ i delta_ {i, j} w ^ j et ne nécessite pas d'introduire de convecteurs. Comme mentionné ci-dessus, je pense que l'utilisation de véritables covecteurs qui ne sont pas une forme cachée de produits scalaires est plutôt limitée dans la plupart des applications. Les gens écrivent simplement v 'pour pouvoir utiliser une syntaxe matricielle pratique, mais ce qu'ils font vraiment, c'est calculer des produits internes, etc. qui sont définis par rapport à des vecteurs, pas à des covecteurs.

En aparté, il y avait une remarque auparavant sur l'associativité de la multiplication si nous aurions v '== v comme dans la proposition originale de Stefan. Cependant, même sans cela, je ne dirais pas que * est associatif, même si v 'est un covecteur qui n'est pas identifié à des vecteurs ordinaires:
A_ (v'_w) est une matrice
(A_v ') _ w produit une erreur

Le gradient d'une fonction à valeur scalaire est en effet une des applications propres des covecteurs, c'est-à-dire sans argument le gradient est un covecteur. Ce qui est implicitement supposé dans des applications comme le gradient conjugué, c'est qu'il existe une métrique (et en fait une métrique inverse) pour mapper ces covecteurs en vecteurs. Sinon, il ne sert à rien de faire quelque chose comme x ^ i (vecteur de position) + alpha g_i (gradient). La bonne façon d'écrire ceci est x ^ i + alpha delta ^ {i, j} g_j où delta ^ {i, j} est la métrique inverse. Si l'on essaie de définir des techniques d'optimisation sur une variété avec une métrique non triviale, alors il faut prendre en compte cette métrique (inverse). Il y a un joli livre à ce sujet:
http://sites.uclouvain.be/absil/amsbook/
et un package matlab correspondant:
http://www.manopt.org

Le 22 octobre 2014, à 22h52, goretkin [email protected] a écrit:

Puisque nous parlons de DFT et de normes ... cela m'a traversé l'esprit aujourd'hui

si f est une fonction d'un vecteur et produit un scalaire, alors je voudrais que diff (f) soit une fonction qui accepte un vecteur et produit un covecteur de sorte que f (x) ~ = f (x0) + diff (f) ( x0) * (x-x0). Une utilisation très courante du gradient consiste à obtenir une approximation linéaire du changement incrémentiel dans la sortie de la fonction étant donné un changement incrémental dans l'entrée. Si l'entrée est un vecteur, alors il semble naturel que le dégradé soit un covecteur.

Mais si je devais mettre en œuvre la descente de gradient, j'aurais besoin d'ajouter (une version mise à l'échelle de) le gradient en x à lui-même. En ce sens, le gradient est un vecteur qui pointe dans la direction la plus raide, dont la norme est proportionnelle au taux de changement le long de cette direction la plus abrupte.

Mon instinct dit que le dégradé est un covecteur est plus important.

-
Répondez directement à cet e-mail ou affichez-le sur GitHub.

A_ (v'_w) est une matrice
(A_v ') _ w produit une erreur

Oh, merde.

b2e4d59001f67400bbcc46e15be2bbc001f07bfe05c7c60a2f473b8dae6dd78a

Ce fil était en retard pour un article contenant une image humoristique.

@Jutho J'avoue que je n'utilise pas la forme la plus générale d'espace dual, mais je ne vois pas en quoi ma description est du tout circulaire compte tenu de mon choix de définir un espace dual en utilisant le cadre d'un espace de Banach. Le formalisme de l'espace de Banach est déjà exagéré pour les espaces vectoriels de dimension finie de toute façon.

Circulaire n'est en effet pas le bon terme. Ce que j'essayais de dire avec ce paragraphe, c'est que je peux inverser ce raisonnement concernant l'évaluation efficace des entrées, puisque par exemple dans le cas du gradient (conjugué) sur des variétés courbes, je suppose que le gradient (covector) g_i peut être calculé efficacement, mais le vecteur correspondant A ^ {i, j} g_ {j} qui sera ajouté à x ^ i (avec A ^ {i, j} la métrique inverse) peut ne pas être efficace. Je viens de remarquer à quel point mon message précédent était mal présenté sur Github; c'était bien quand j'ai écrit l'e-mail. J'ai essayé de le réparer maintenant. Je ne veux pas continuer à me répéter, ni donner la (fausse) impression de ne pas être d'accord avec les propositions actuelles. Les seuls points que j'essayais de faire valoir.

  1. L'espace double est certainement un espace vectoriel, ses éléments sont donc des vecteurs. Cependant, le but ne doit pas être que tous les objets qui ont la signification mathématique d'un vecteur soient un sous-type de AbstractVector de Julia, ni que tous les objets qui sont des sous-types de AbstractVector aient les caractéristiques mathématiques appropriées d'un vecteur. En ce sens, j'aime bien plus le nom Matrix que le nom Vector , car il a beaucoup moins de connotation. Par conséquent, je suppose que je peux être d'accord avec le fait que, quel que soit le type de Covector introduit dans le cadre de cette proposition, il ne devrait pas être un sous-type de AbstractVector ou même AbstractArray , même dans les cas où V * est naturellement isomorphe à V (espace cartésien, qui représente probablement la moitié des applications).
  2. Le produit interne vous permet de construire un mappage de V à V_, mais ce n'est pas une condition préalable à l'existence de V_. Certes, cela semble être un problème non pertinent car dans la programmation, vous avez toujours un espace vectoriel de dimension finie et il y a toujours un produit interne (même si ce n'est peut-être pas celui pertinent pour l'application), mais le point pratique que j'essayais de faire est-ce. Il existe des applications appropriées de covecteurs en programmation, comme le bel exemple de gradient de @goretkin , mais dans la plupart des applications où les gens écrivent v' , ils n'essaient pas vraiment de construire un covecteur, ils essaient juste d'écrire un bilinéaire mappage entre deux vecteurs (ie V x V en scalaire), comme dans v'_A_w = v ^ i A_ {i, j} w ^ j ou même v'w = v ^ i delta_ {i, j} w ^ j en utilisant la représentation matricielle pratique. Je préfère écrire dot(v,A*w) explicitement, mais c'est un choix personnel. Comme nous n'avons aucune information sur les indices supérieurs ou inférieurs sur les matrices, on peut construire des cas d'utilisation où dans une expression scalaire comme v'*A*w deux v' et w peuvent être des vecteurs ou des covecteurs au sens mathématique. La seule conséquence de ceci est que je ne suis pas sûr que je sois vraiment content d'appeler le type de v' Covector , tout comme le nom Vector il a trop de mathématiques connotation qui pourrait ne pas être justifiée dans la plupart des applications, ce qui conduit alors à davantage de discussions (pour la plupart non pertinentes) comme celle-ci.

@jutho +1 pour le gradient étant des covecteurs
J'ai appris cela pour la première fois grâce à la thèse de doctorat de Steve Smith et j'ai utilisé cette idée
pour expliquer clairement cette vague idée de gradient conjugué pour les calculs de valeurs propres
dont les gens parlaient en chimie et en physique.

J'ai été de plus en plus époustouflé par la cohérence interne
et autonome est le monde des tenseurs de rang 2 avec un index supérieur et un index inférieur.
C'est ce que nous appelons conventionnellement des calculs matriciels ou simplement une ancienne algèbre linéaire.
Heck, alors que je peux trouver beaucoup d'exemples comme norm (v, p) ou fft (v) où les fonctions
diffèrent si ce sont des vecteurs ou des covecteurs, je n'ai vraiment pas un seul bon exemple (encore!)
d'une fonction naturellement différente sur un vecteur et une matrice à une colonne.
(Quelqu'un m'aide, il doit sûrement y en avoir un, voire plusieurs !!)

Je suis aussi depuis plusieurs années préoccupé comme @jutho ce vecteur abstrait
les espaces n'avaient pas encore trouvé leur chemin dans Julia, je me souviens avoir discuté avec @StefanKarpinski
à ce sujet sur mon tableau blanc il y a des années. Toujours les préoccupations primordiales de
1) Les nouveaux arrivants chez Julia doivent avoir une expérience facile et
2) performances
doit l'emporter sur ce truc de fantaisie.

Après avoir parlé avec
Algèbre linéaire (tableaux avec un contra et un co) et il existe des tenseurs simples
(tout le monde est un co, pas de contras). Ce dernier fonctionne bien dans APL et Mathematica.
Le premier est une algèbre linéaire et a été mieux capturé dans MATLAB avant
ils se sont greffés sur des tableaux de dimension supérieure à 2. Il y a la plus grande généralité
qui après un mot avec @JeffBezanson semblait que ce n'était pas si fou.

au fait, je pense que cela a pris environ un an sinon plus, mais nous prenons finalement cela au sérieux :-)

En ce qui concerne
A_ (v'_w) est une matrice
(A_v ') _ w produit une erreur

Seule la multiplication matricielle "*" est associative. La matrice de temps scalaire surchargée n'a jamais été
associatif. Pas même en maths, pas même en MATLAB.

A_ (v'_w) est une matrice
(A_v ') _ w produit une erreur

Bonne prise. Y a-t-il des cas où des non-erreurs produisent des réponses différentes? Une question connexe: que devrait faire un covecteur scalaire *?

La matrice des temps scalaires surchargée n'a jamais été associative. Pas même en maths, pas même en MATLAB.

Les opérations n'impliquant que des matrices et des scalaires sont associatives (voir point 3). Comme nous pouvons traiter les vecteurs comme des tableaux à 1 colonne, leur inclusion ne pose pas non plus de problème. Le problème ici est que le Covector est un opérateur qui peut réduire les dimensions (mapper les vecteurs en scalaires), donc je ne sais pas comment cela s'intègre.

L'ajout de matrice scalaire est une plus grande préoccupation, car cela ruine la distributivité (bien que si je me souviens bien, je pense que ce débat a résolu de le garder):

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

Ceci est un résumé extrêmement agréable:

Après avoir parlé avec
Algèbre linéaire (tableaux avec un contra et un co) et il existe des tenseurs simples
(tout le monde est un co, pas de contras).

Il est clair que cette discussion ne concerne pas le support du cas plus général, c'est trop déroutant pour les personnes qui n'ont pas besoin de la généralité complète, ne peuvent pas être incluses dans la hiérarchie AbstractArray actuelle et sont plus adaptées à un package (sur lequel je travaille actuellement sur). Mais la discussion est en effet, lequel de ces deux mondes spéciaux voulons-nous soutenir, car ils ne sont pas mutuellement compatibles.

Dans le premier, tous les vecteurs v sont des colonnes, tous les v' sont des covecteurs et toutes les matrices sont des opérateurs linéaires V-> W (par exemple, ils vivent dans W ⊗ V_) Ceci exclut la représentation de par exemple bilinéaire mappe V x V -> scalaire (par exemple v ^ i A_ {i, j} w ^ j) car cela nécessite une matrice avec deux indices inférieurs (par exemple vivant dans V_ ⊗ W_). Bien sûr, vous pouvez toujours l'utiliser dans la pratique et l'écrire comme v'_A*w , mais cela entre en conflit avec la nomenclature choisie des objets. De plus, il ne spécifie pas dans quel espace les tableaux d'ordre supérieur vivent.

Ce dernier cas résout ce problème puisqu'il a (V == V *), mais conduit à des résultats surprenants comme v' == v . De plus, cette solution est en fait limitée aux espaces vectoriels réels, car même dans les espaces complexes avec un produit interne euclidien standard (ie `` espace cartésien complexe ''), l'espace dual n'est pas naturellement isomorphe à V mais à conj (V) (V bar), l'espace vectoriel conjugué (voir les espaces de Hilbert dans http://en.wikipedia.org/wiki/Complex_conjugate_vector_space)

En ce qui concerne la non-associativité, à cet égard, le comportement actuel, où v'*v ne produit pas de scalaire mais un tableau, est en fait plus cohérent, puisque à la fois A*(v'*v) et (A*v')*v produit une erreur.

Le côté "Algèbre linéaire" des choses peut en outre être résolu en différents choix de représentation des vecteurs et des covecteurs:

  • La proposition Covector, alias "funny row vector", dont nous avons discuté récemment.
  • La sémantique "pas de vrais vecteurs" qui confond (N-vecteurs comme Nx1-matrices), (N-lignes-vecteurs comme 1xN-matrices), qui est approuvée par Matlab et autres.
  • La manière actuelle de Julia - ne pas confondre les N-vecteurs denses et les matrices Nx1, confondre les N-vecteurs clairsemés et les matrices Nx1, représenter les vecteurs N-lignes comme des matrices 1xN.

Je me demande s'il est strictement nécessaire de confondre aussi (scalaires, 1-vecteurs et 1x1-matrices) dans le monde des "pas de vrais vecteurs"; @alanedelman et moi en discutions hier et en algèbre linéaire numérique, la commutativité du vecteur * scalaire et scalaire * vecteur est utilisée partout, mais le vecteur * produit scalaire est celui qui ne se soucie pas de savoir s'il fait (N,) * ( ,) ou (N, 1) * (1,1).

1) En fin de compte, les éléments les plus importants sont A) la facilité d'utilisation et B) les performances.
2) Les deux mondes doivent pouvoir coexister en parfaite harmonie. Lors de travaux d'ingénierie, je pense qu'il n'y a rien de plus intuitif et facile que la notation tensorielle. Une suggestion, si je peux, pourrait être d'activer un indicateur d'environnement ou un package pourrait être importé au début d'un programme. Cela permettrait une facilité d'utilisation et une logique de programmation simple. N'est-ce pas possible ou devons-nous choisir un schéma global?

Il semble qu'il y ait deux options
1) permettre l'importation d'une boîte à outils, d'un correctif ou d'un indicateur environnemental pour fonctionner simultanément
2) Construisez un langage capable d'unifier les mondes spéciaux, tout en restant facile à utiliser et rapide

Je ne sais pas à quel point cela est prometteur, mais je suis juste tombé sur un domaine de recherche potentiellement intéressant.

Puis-je attirer votre attention sur:

Groupe de recherche sur l'algèbre géométrique
http://www.mrao.cam.ac.uk/~clifford/pages/introduction.htm

Cours magistral en algèbre géométrique
http://www.mrao.cam.ac.uk/~clifford/ptIIIcourse/course99/

Applications physiques de l'algèbre géométrique
http://www.mrao.cam.ac.uk/~clifford/ptIIIcourse/

Un langage mathématique unifié pour la physique et l'ingénierie au 21e siècle
http://www.mrao.cam.ac.uk/%7Eclifford/publications/ps/dll_millen.pdf

Algèbre géométrique
http://arxiv.org/pdf/1205.5935v1.pdf

Fondements de l'algèbre géométrique
http://link.springer.com/book/10.1007%2F978-3-642-31794-1

Calcul d'algèbre géométrique
http://link.springer.com/book/10.1007%2F978-1-84996-108-0

Après avoir examiné cela un peu plus, cela semble vraiment incroyable.

Tant que l'algèbre et la géométrie ont été séparées, leurs progrès ont été lents et leurs utilisations limitées; mais lorsque ces deux sciences se sont unies, elles se sont prêtées chacune des forces mutuelles et ont marché ensemble vers la perfection.

  • Joseph Louis Lagrange

Imaginez avoir besoin de lunettes et ne pas savoir que vous en aviez besoin. Ensuite, lorsque vous obtenez des lunettes, le monde change de manière inattendue. GA est comme des lunettes pour l'intérieur de votre cerveau.

  • Pablo Colapinto

Ce gars semble avoir la bonne idée ... https://github.com/wolftype/versor

Les bibliothèques d'opérations matricielles typiques ont des fonctions intégrées pour la multiplication vectorielle et matricielle. L'algèbre géométrique combine de nombreuses autres mathématiques (algèbres matricielles, tensorielles, vectorielles et de mensonge). Versor est similaire, mais sur les stéroïdes, où les vecteurs et les matrices clairsemées de différentes tailles sont tous simplement appelés multivecteurs et représentent des éléments géométriques au-delà des directions xyz et des matrices de transformation. Les cercles, les lignes, les sphères, les plans, les points sont tous des éléments algébriques, tout comme les opérateurs qui tournent, tordent, dilatent et plient ces variables. Ces éléments et ces opérateurs sont tous deux des multivecteurs qui se multiplient de nombreuses manières différentes.

Cette vidéo donne de bons détails sur Versor, le langage mathématique GA programmé.
https://www.youtube.com/watch?v=W4p-e-g37tg

Ce sont les diapositives pour cela. https://github.com/boostcon/cppnow_presentations_2014/blob/master/files/generic_spaces.pdf

Vous pouvez faire des calculs de tenseurs incroyables, très facilement, optimisés pour la vitesse lorsque vous compilez.

@ esd100 , je pense qu'il serait utile de garder la discussion sur ce fil centrée sur la question spécifique dans le titre.

@johnmyleswhite J'ai fait une recherche sur le terme "tenseur" et il a été mentionné 171 fois (maintenant 172 fois). Bien que je sois d'accord avec votre déclaration, en général, ce fil contient 157 commentaires (maintenant 158), dont certains traitent directement et indirectement du titre du message original (Prendre le vecteur au sérieux). Je pense que mon article traite indirectement du titre de l'article original en offrant une perspective accrue de ce qui peut être fait avec les nouvelles applications des mathématiques tensorielles, via l'algèbre géométrique. Mon opinion est que la construction du genre de pouvoir de dimension supérieure que Versor a dans Julia serait une caractéristique bénéfique supplémentaire de Julia. Le titre de la vidéo YouTube était «Programmation générique d'espaces génériques: Algèbre géométrique à la compilation avec C ++ 11». Je ne vois pas pourquoi cela ne pourrait pas être Julia au lieu de C ++. Là encore, je ne suis ni mathématicien ni informaticien.

@ esd100 Les algèbres géométriques sont intéressantes, mais ne sont pas la chose la plus générale couverte par ce numéro. L'algèbre géométrique qui nous intéresserait principalement est l'algèbre réelle de Clifford Cl (R ^ n, I); cependant, nous serions également intéressés par d'autres algèbres de Clifford qui ne sont pas des algèbres géométriques, telles que les algèbres de Clifford comme sur des vecteurs complexes Cl (C ^ n, I) ou même des algèbres sur des champs arbitraires ou des anneaux non commutatifs Cl (F ^ n, I) . De plus, nous ne voulons même pas nous limiter nécessairement aux algèbres de Clifford, mais nous voulons examiner comment l'algèbre linéaire se généralise au cadre plus général de l'algèbre tensorielle où les formes quadratiques (produits internes) ne sont pas nécessairement définies.

Dans le cadre de l'algèbre tensorielle, la proposition « v' est un no-op» dans l'OP est parfaitement logique, car elle se généralise de manière cohérente à l'antiautomorphisme d'inversion . Cependant, ce n’est qu’une des trois propositions au moins sur la table. On pourrait également envisager des extensions bialgébriques qui conduisent à des gèbres de charbon tensoriels où la proposition "v 'produit un covecteur" est très naturelle. Je ne sais pas comment la proposition "pas de vrai vecteur" se généralise en une algèbre tensorielle.

@jiahao Merci pour votre réponse très informative. C'est bien d'avoir quelqu'un qui maîtrise le sujet pour faire la lumière. Je serai intéressé à approfondir ces sujets pour avoir une meilleure compréhension. Je suis curieux de savoir pourquoi un code super optimisé pour BLAS existe, mais un code super optimisé pour des algèbres plus complexes, comme Clifford Algebra ne le fait pas.

Je ne sais pas si cette discussion est toujours en cours, mais je voulais partager mon opinion car ces problèmes sont parmi les parties les plus importantes (et ennuyeuses) de moi en utilisant Julia. Je suis d'accord avec certains des éléments ci-dessus et d'autres non.

Fondamentalement, je pense que nous devons réaliser ceci: tous les utilisateurs de Julia voudront des tableaux rapides et multidimensionnels (pour le stockage de données) comme déjà implémentés dans Julia. Beaucoup / la plupart des utilisateurs seront également intéressés par l'algèbre linéaire, et les tableaux mentionnés ci-dessus sont un moyen pratique de conditionner les données contenues dans des vecteurs et des matrices. Quelques utilisateurs voudront faire de l'algèbre tenseur générique (multilinéaire).

La question est de savoir comment étendre ces tableaux «nus» aux concepts mathématiques de l'algèbre linéaire? Comment faire pour que la syntaxe ressemble aux mathématiques normales? Les gens seront-ils à l'aise que v ''! = V? Etc.

La première chose est que nous ne voulons pas aggraver les tableaux, juste pour pouvoir faire de l'algèbre linéaire. Nous ne voulons pas ajouter de données supplémentaires à Vector ou des arguments de modèle inutiles à Array. Nous voulons être aussi rapides que C / C ++ lorsque nous ne faisons pas d'algèbre linéaire (et, espérons-le, aussi rapides que C / fortran, quand nous le faisons).

La deuxième chose que nous devons tous comprendre, c'est que la contraction tenseur multidimensionnelle complète nécessite des quantités importantes d'informations supplémentaires. Pour les tenseurs jusqu'à 2 dimensions, écrivez n'importe quelle multiplication comme A_B'_C * D ... tandis que pour les tenseurs généraux, il est plus naturel de penser à un graphe pour définir la contraction (un diagramme de réseau de tenseurs, ou un graphe de facteurs - la même chose va par de nombreux noms différents). Pour les tenseurs de haute dimension, il est logique d'envelopper les tableaux nus et de les décorer avec des espaces vectoriels, etc. pour garder une trace de tout. Cela peut être fait dans des packages tels que ceux sur lesquels Jutho (et éventuellement d'autres) travaillent. Il ne sert à rien de considérer la signification de 'sur des tenseurs de dimension 3+ - personne ne serait intéressé à utiliser cette fonction lorsque des permutés existent, ou s'ils utilisent un package de tenseurs dédié.

Les utilisateurs principaux devront multiplier les matrices, ajouter des vecteurs et ainsi de suite. Ils voudront transposer des matrices. Ils voudront également un produit intérieur et un produit extérieur. Qu'on le veuille ou non, ceux-ci sont définis en conjonction avec un double espace. Nous savons ce que cela signifie pour les nombres réels et les nombres complexes, et les avons prédéfinis. Et oui, c'est vrai que le double espace d'un véritable espace vectoriel fini ressemble fondamentalement à la même chose, et je crois que c'est ce qui obscurcit tout le problème. Le plus gros problème actuel est que nous n'avons pas de vecteur double approprié. Sans cela, nous ne pouvons pas «écrire» d'équations dans Julia comme nous le faisons sur papier et stylo - un énorme problème! Plus important encore, nous n'avons pas v '' = v. Ceci est très peu intuitif.

Les gens voudront ou auront seulement besoin de créer un double vecteur pour réaliser des produits intérieurs ou extérieurs. Un simple wrapper décoratif pour dire au compilateur ce qu'il doit faire lors de sa prochaine rencontre * est une solution judicieuse et ne devrait avoir aucun temps d'exécution. Je pense que ces doubles vecteurs / covecteurs devraient être indexables, additionnables / soustractibles, multipliés par un scalaire, multipliés par un vecteur (renvoyant un scalaire) et multipliés par une matrice (renvoyant un covecteur) - et c'est à peu près tout! Je n'effectuerais même pas explicitement la conjugaison complexe pour les vecteurs complexes - qui pourrait être intégrée au produit scalaire, au produit externe, à l'indexation, etc. pour des améliorations de vitesse / mémoire.

Je ne crains pas que nous ajoutions des complications au système de type. Je crois que cela est nécessaire pour encapsuler les mathématiques telles que les utilisateurs les ont apprises au lycée et à l'université: avoir associatif * (où bien défini), avoir v '' = v, avoir à la fois des "bras" et des "kets" (je j'utilise ici la notation Dirac de l'algèbre linéaire), etc.

BTW si ce truc a été implémenté dans Julia 0.4, je suis inconscient! Je travaille toujours sur la compréhension de 0.3.4 ...

@andyferris , je suis généralement d'accord avec tout cela. Je pense que ma proposition ici pourrait être légèrement modifiée et fonctionner bien: au lieu que M*v' soit une erreur, faites-lui produire un covecteur et au lieu que v*M soit une erreur, faites-le produire un vecteur. Pour moi, cela revient conceptuellement à M être indépendant quant à savoir si ses dimensions sont à la hausse ou à la baisse - vous pouvez écrire v'*M*w ou v*M*w' pour un produit interne et l'un ou l'autre fonctionnera.

Une idée qui a rebondi est d'avoir des tableaux de lignes principales et de colonnes majeures et d'avoir M.' juste changer de l'un à l'autre et de même avoir v' être la variation de ligne principale de v - qui a la même forme mais est stocké conceptuellement dans l'autre ordre. Pas sûr à ce sujet cependant.

+1 à @andyferris . Je pense aussi que nous voulons juste des types de wrapper simples Transpose et ConjTranspose qui nous permettent d'utiliser la syntaxe concise de l'algèbre matricielle pour écrire des produits de vecteurs, des matrices et des transpositions de celles-ci. En ce qui concerne le point 2 de la proposition de @StefanKarpinski , je ne limiterais pas ces wrappers à un conteneur d'objets AbstractArray et je ne ferais pas partie des types de la hiérarchie de types AbstractArray elle-même. Transpose(x) devrait juste être le type qui résulte de l'écriture de x' dans une expression et permet de différer son évaluation / d'avoir une évaluation paresseuse, en fonction du reste de l'expression, en distribuant sur Transpose pour le reste des opérations dans l'expression (qui sera probablement l'opérateur de multiplication * dans 99,9% des cas). Cependant, les gens devraient également pouvoir donner à cette syntaxe une signification pour les nouveaux types qui pourraient ne pas faire partie de la hiérarchie AbstractArray , tels que les objets de factorisation matricielle ou d'autres types pour définir par exemple des opérateurs linéaires.

Pour le cas spécifique des matrices et des vecteurs, je ne vois pas vraiment le cas d'utilisation de M*v' , mais je n'y suis pas intrinsèquement opposé. L'important est qu'il réponde aux attentes de base (c'est-à-dire les règles 2, 4, 5, 6 et 8 de la fin de la proposition de Stefan).

Le principal point final de la discussion est probablement l'interaction avec le tranchage. Une tranche de ligne d'une matrice devrait-elle être automatiquement un Transpose ? Mon vote ici porte sur les règles de découpage APL là où ce n'est pas le cas.

@Jutho , la motivation est de faire en sorte que * associatif - A*(v'w) et (A*v')*w fonctionnent tous les deux et produisent les mêmes résultats, modulo la non-associativité du type d'élément sous-jacent (ex. point).

Pour que (A*v')*w donne le même résultat que A*(v'*w) , A*v' devrait être un tableau N=3 . Est-ce ce que tu avais en tête? J'ai complètement raté ça.

OK, très intéressant, j'ai quelques commentaires.

Nous devons d'abord nous adresser aux principaux utilisateurs. Fondamentalement, utiliser d'abord et avant tout Array{T,n} comme n stockage dimensionnel est sa fonction principale. Cela doit avoir un sens pour les programmeurs qui n'ont aucune idée de l'algèbre linéaire mais qui veulent manipuler des données dans Julia. Pour cette raison, en aucun cas une tranche de tableau ne peut renvoyer un co-vecteur! Il s'agit d'un concept d'algèbre strictement linéaire qui ne s'applique qu'à certains types de données T qui peuvent définir un espace vectoriel et son dual (par exemple des données numériques ou des "champs").

Je pourrais aller dans les deux sens avec le tranchage APL. Cela semble assez intuitif. Il est de type stable et ne fait rien de surprenant. Bien que je ne pense pas qu'il soit nécessaire de faire ce changement pour rendre l'algèbre linéaire auto-cohérente ... cela pourrait être bien (même si cela peut être un changement radical). Je trouve troublant que M[1,:] soit de taille 1xn et M[:,1] soit de taille n (pas nx1) ... cela semble trop asymétrique ... mais je suppose que c'est un bon rappel sur la façon dont les choses sont mis en mémoire. Quoi qu'il en soit, ce changement ne devrait être fait que si cela a un sens pour le stockage et la manipulation de données abstraites. Pour moi, ce changement est orthogonal à la discussion d'algèbre linéaire.

Ainsi, même si nous n'implémentons pas les règles de découpage APL, nous pouvons toujours récupérer une algèbre linéaire significative assez simplement. Si vous voulez que i e vecteur colonne de M appeler colvec(M,i) et si vous voulez que le i e co vecteur de rangée M appel rowvec(M,i) . Si i était un tuple ou un vecteur, il pourrait renvoyer un tuple ou un vecteur ou des (co) vecteurs (cela pourrait-il être utile pour raisonner sur la parallélisation dans certains cas?). Si vous préférez une matrice, utilisez simplement la notation d'indexation normale. Si nous utilisons des règles de découpage APL, la même chose est très utile pour distinguer l'action des tranches de ligne et de colonne avec le symbole * . (Il faut considérer le comportement de la conjugaison complexe avec rowvec ).

De cette façon, si vous faites de l'algèbre linéaire, tout aura un sens et l'utilisateur n'aura pas à se soucier de la règle de découpage particulière implémentée par Julia. L'opérateur ' n'agirait que sur les vecteurs et les covecteurs (pour changer la décoration) et sur les matrices (soit de manière directe ou paresseuse). Il est peut-être plus facile de se rappeler comment extraire des vecteurs de base d'une composition propre, ce que j'ai toujours l'air d'oublier.

Pour * , je pense que l'algèbre matricielle / vectorielle de Julia devrait fonctionner pour qu'elle ressemble et se sente aux mathématiques en tant qu'opérateur de multiplication et jamais à l'opérateur de produit tensoriel entre deux vecteurs, deux covecteurs, deux matrices, etc. ne devrait pas autoriser M*v' , car strictement en mathématiques, vous devez écrire l'équation avec le symbole "\ otimes" (le signe des temps à l'intérieur d'un cercle) et non le symbole de multiplication standard. En utilisant le symbole de multiplication, sa signification est mal définie! Nous ne pouvons pas avoir w*M*v' == v'*M*w car la multiplication matricielle n'est pas commutative. Encore une fois pour M*v'*v cela n'a pas de sens de parler d'associativité de * . Vous pouvez soit interpréter cela comme innerproduct(outerproduct(M,v'),v) qui nécessite deux symboles de multiplication différents, ou comme une multiplication scalaire M * innerproduct(v',v) où il est judicieux d'utiliser * pour les deux. Avec cette expression, nous pourrions avoir besoin de crochets, ou peut-être que le compilateur pourrait rechercher un ordre d'opérations significatif (notez ici que l'ordre d'évaluation le plus rapide est aussi ce que je considère comme le seul ordre d'évaluation valide).

Avec des vecteurs, des covecteurs et des matrices, nous pouvons avoir un système algébrique cohérent avec les mathématiques standard. Chaque fois que vous souhaitez effectuer le produit externe entre deux vecteurs, ou deux covecteurs, ou deux matrices, vous passez intrinsèquement à l'algèbre multi-linéaire ou à l'algèbre tensorielle générique. Ici, vous pourriez avoir des matrices et des vecteurs qui se multiplient comme kron(M,N) utilisant un nouveau symbole. Ou vous pouvez demander aux utilisateurs d'utiliser un package d'algèbre multi-linéaire complet. Ou peu importe ... cela semble au-delà de la portée de la question initiale (qui était il y a 14 mois, au fait ...)

Donc, pour résumer, je vois quatre choses presque complètement distinctes qui se passent ici:

  1. Améliorez le découpage de Array s de données arbitraires à l'aide des règles de découpage APL.
  2. Rendre l'algèbre linéaire dans Julia plus intuitive et pleinement cohérente avec les mathématiques en ajoutant quelques concepts simples: des covecteurs et une méthode d'extraction de vecteurs et de covecteurs à partir de matrices, et des opérations entre covecteurs, matrices, etc. l'utilisation des matrices 1xn et nx1 continuera à fonctionner comme prévu et le vecteur se comportera de la même manière.
  3. Pour la vitesse, implémentez l'évaluation paresseuse pour transpose , conj , etc. en utilisant des types de wrapping un peu comme covector, mais aussi pour les matrices.
  4. Développez un package de tenseurs à usage général et ajoutez-le éventuellement à la bibliothèque standard.

Peut-être que seul le premier sera un changement radical? Les autres améliorent ou ajoutent aux fonctionnalités actuelles. Ils peuvent tous être mis en œuvre plus ou moins indépendamment, et ce sont probablement tous des choses intéressantes à faire. (PS si vous voulez du slicing APL, je vous recommande de le faire bientôt , avant que Julia ne devienne trop "grosse" et qu'il casse trop de paquets, etc.).

Oui, désolé, ma suggestion d'autoriser M*v' n'a vraiment aucun sens puisque les différentes façons d'associer des produits produisent des formes complètement différentes. Donc, je pense que si nous allons changer cela, ma proposition initiale est la voie à suivre. Jusqu'à présent, cela semble être la meilleure combinaison avec le comportement de découpage APL. Bien sûr, que nous voulions ou non un comportement de découpage APL est une considération de plus haut niveau.

Je voulais juste dire quelques choses et veuillez les prendre avec un grain de sel. Ce ne sont que des opinions et je sais que les miennes n'ont pas beaucoup de poids. Cependant, une chose m'a frappé pendant que je lisais le commentaire de

@ esd100 C'est vrai, j'y ai pensé. Mais avec Julia, je pensais que nous voulions être _gorgés _... voulions répondre à de nombreux objectifs et exceller dans de nombreux domaines. Il peut y avoir de véritables raisons scientifiques pour manipuler des données non numériques. Il peut y avoir des scientifiques actuels ou d'anciens utilisateurs de Julia qui souhaitent faire de la programmation plus générale. Mon point précédent était que nous ne devrions pas ralentir Array ou prendre plus de mémoire en lui donnant un champ supplémentaire pour parler de transposition / conjugaison.

Si l'on effectue un découpage APL, les tranches de ligne ou de colonne doivent renvoyer le même objet. La question était: sans découpage APL, quelle est la meilleure chose à faire pour M[1,:] ? Eh bien, si M[1,:,:,:,:] renvoie un Array{T,n} , je pense que cela dérouterait tout le monde si M[1,:] retournait un covector{T} . Que faire si nous laissons M = zeros(3,3,3) et appelons M[1,:] , qui renvoie actuellement une matrice 1x9? Devrions-nous en faire un covecteur aussi? Et si M était Array{String,3} ?

Cela me semble assez surprenant et déroutant. Ce n'est pas non plus cohérent avec les changements de découpage APL, si cela devait se produire à l'avenir. Ainsi, je suggérais d'ajouter de nouvelles fonctions à des fins d'algèbre linéaire, rowvec(M,i) et colvec(M,i) . Ce n'est pas idéal, cela ajoute de nouvelles fonctions ... mais au moins, il est clair ce qu'elles devraient renvoyer à des fins d'algèbre linéaire. C'était la seule chose à laquelle je pouvais penser qui avait de belles propriétés pour les tableaux génériques et de belles propriétés pour l'algèbre linéaire (avec un type de covecteur), sans essayer de répondre à l'algèbre multi-linéaire. Si quelqu'un a une meilleure notation pour extraire les covecteurs des matrices, ce serait bien aussi!

@ esd100 : Je suis presque sûr que les programmeurs sont le public pour lequel Julia est conçue. L'informatique scientifique est la force de Julias, mais de nombreux utilisateurs de Julia l'utilisent comme un langage de programmation à usage général.

Je suis d'accord avec @andyferris qu'il faut séparer les exigences du tenseur des exigences du conteneur.

Sans avoir lu tout le fil de discussion, mon problème personnel est que si j'ai un tableau 3D A (par exemple des données tomographiques) et

imagesc( data[1,:,:] )

cela ne fonctionne pas. IMHO data[1,:,:] , data[:,1,:] et data[:,:,1] devraient être des tableaux 2D (ou sous-tableaux). Actuellement, j'utilise une fonction squeeze auto-définie pour résoudre ce problème.

Encore une fois, ce n'est que mon opinion et une très peu importante, étant donné que je suis loin de l'action. J'ai l'impression que lorsqu'une application est développée, elle devrait avoir un ensemble de principes de conception qui la guident. Les constructeurs doivent envoyer un message clair et unifié sur son objectif et son identité. Si le développement d'une plate-forme informatique rapide, intuitive, sophistiquée et technique est l'objectif et c'est la force de Julia, alors tenez-vous-y. N'envoyez pas de signaux mixtes en essayant d'adapter son objectif au public des programmeurs généraux.

Le fait est que même pour des applications purement scientifiques ou mathématiques, il existe plusieurs interprétations contradictoires, par exemple il y a plusieurs objets mathématiques que vous pourriez représenter à l'aide de vecteurs et de matrices, et par conséquent, il ne faut pas faire de choix trop spécifiques.

Il est préférable d'avoir des packages dédiés pour satisfaire les besoins de disciplines spécifiques suivant un ensemble spécifique de conventions.

@jutho Mon instinct est que vous avez raison, mais je me demande quelle est la base de votre conclusion selon laquelle les packages sont "meilleurs" et par rapport à quelles alternatives?

Ceci est assez simple. La fonctionnalité qui est importante pour la plupart des utilisateurs Julia appartient à Base. Une fonctionnalité plus spéciale appartient à un package.

Bien sûr, il n'est pas très facile de tracer une ligne ici. Mais la meilleure façon d'obtenir quelque chose dans la base est de créer un package afin que de nombreuses personnes puissent le tester. Diverses fonctionnalités de base qui ont atterri dans Base ont d'abord été développées dans un package.

@ esd100 , je ne comprends pas entièrement votre question. En fin de compte, ce qu'un langage scientifique devrait fournir, ce sont les structures de données et les méthodes pour représenter et calculer avec des objets typiques utilisés en science. Mais certaines structures de données peuvent être utiles pour représenter différentes structures mathématiques, et parfois une représentation différente peut être pratique pour des objets du même type. Par conséquent, associer une structure mathématique fixe à un type de données peut être trop restrictif pour certaines disciplines et trop complexe pour d'autres disciplines. Donc, cela ne devrait pas être poursuivi dans la base de Julia, mais par des packages spécifiques qui essaient uniquement de répondre aux besoins d'une seule discipline.

En ce qui concerne la discussion actuelle, tout le monde travaillant avec des vecteurs et des matrices s'attend à la possibilité de transposer un vecteur, et d'avoir v ^ T * w = scalaire. Mais ces vecteurs et matrices pourraient être utilisés pour représenter un certain nombre de choses différentes, et cela dépendra probablement du domaine / de la discipline. J'ai donné des exemples où v ^ T pourrait ne pas être un véritable covector dans les messages ci-dessus.

Le 11 janvier 2015, à 18h10, esd100 [email protected] a écrit:

@jutho https://github.com/jutho Mon instinct est que vous avez raison, mais je me demande sur quoi repose votre conclusion selon laquelle les packages sont «meilleurs» et par rapport à quelles alternatives?

-
Répondez directement à cet e-mail ou consultez-le sur GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -69501771.

Ceci est assez simple. La fonctionnalité qui est importante pour la plupart des utilisateurs Julia appartient à Base. Une fonctionnalité plus spéciale appartient à un package.

Je ne pense pas que ce soit aussi simple. Par exemple, il existe des fonctions Bessel dans Base, et il est très peu probable qu'elles soient importantes pour la plupart des utilisateurs de Julia. La raison pour laquelle ils peuvent être dans Base est qu'il existe une norme raisonnablement universelle pour ce que sont les fonctions de Bessel, et personne n'est susceptible d'utiliser ces noms pour autre chose ou de s'attendre à ce qu'ils fassent quelque chose de différent.

Grâce à des conventions de nommage prudentes (certains pourraient dire verbeuses), Mathematica est capable de mettre plus de 4000 fonctions dans le langage de base, et je trouve extrêmement pratique de ne presque jamais avoir à charger un paquet. En revanche, lorsque j'utilise Python / Sage, il n'est pas inhabituel pour un fichier / bloc-notes d'importer explicitement 200 fonctions en haut (en évitant la syntaxe "from ___ import *", plus introuvable). Chaque fois que j'ai besoin d'utiliser une fonction qui n'est pas intégrée, je dois passer par un tas d'étapes supplémentaires:

(1) Essayez de vous souvenir si je l'ai déjà utilisé dans ce fichier et / ou effectuez une recherche de texte pour confirmer (en vous limitant à des mots entiers si le nom est une sous-chaîne de quelque chose d'autre)
(2) Soit: (a) ajouter l'import tout de suite, perdre le fil de mes pensées et le retrouver; ou (b) essayez de vous souvenir d'ajouter l'importation après avoir fait ce que je faisais, en gardant une autre chose en mémoire
(3) Pour les fonctions moins courantes, il faut peut-être chercher dans quel paquet il se trouve

Cela peut devenir ennuyeux et débilitant. Donc, je pense que, comme les fonctions Bessel, tout ce qui est considéré comme standard - et donc ne causera pas de conflits de noms ou de confusion - devrait être dans Base, que beaucoup de gens l'utilisent ou non. Algèbre certainement linéaire.


De retour au sujet, nous avons parlé de plusieurs couches de fonctionnalités - allant des tableaux en tant que conteneurs sémantiquement nus (tels qu'implémentés dans Base.AbstractArray ), aux objets tenseurs cartésiens avec une sémantique up / down (comme décrit par mon Proposition AbstractTensorArray , à des objets tenseurs plus généraux avec des indices mappés sur des espaces vectoriels (comme dans TensorToolbox de @Jutho ) --- de généralité croissante et de potentiel d'optimisation décroissant.

Je pense qu'il est important que les utilisateurs puissent facilement passer d'une couche à l'autre, notamment parce que les utilisateurs _peuvent ne pas savoir_ de quel niveau de généralité ils ont réellement besoin lorsqu'ils commencent. À titre d'exemple simple, @Jutho a souligné que dans l'exemple de traitement d'image de @jdbates , les utilisateurs pourraient très bien vouloir associer différents sous-ensembles d'indices à des géométries distinctes, de manière à traiter les coordonnées de l'image d'une manière et l'espace colorimétrique d'une autre manière; mais s'ils avaient commencé à utiliser un seul de ces éléments géométriquement, il devrait être pratique de procéder à une conversion ascendante vers une représentation plus générale qui permet d'utiliser chacun avec sa propre géométrie. Si les fonctions supplémentaires (ou les modèles d'appel de fonction) qui interviennent à chaque niveau de généralité utilisent des valeurs par défaut sensibles - par exemple, remonter / descendre les indices et les indices neutres dans AbstractTensorArray s vers des espaces vectoriels cartésiens par défaut uniques et espaces "séquence", respectivement --- alors cela devient presque transparent. Les utilisateurs d'une couche inférieure de fonctionnalités n'ont pas besoin de connaître ou de se soucier des couches supérieures, jusqu'à ce qu'ils en aient besoin.

Toute partie de cela pour laquelle il est clair et prévisible - pour les utilisateurs concernés - ce que la hiérarchie devrait être, pourrait raisonnablement aller dans Base. La vraie question devrait être - que ce soit pour les opérations de tableau, l'algèbre linéaire ou l'algèbre tensorielle - quelle est la probabilité qu'un utilisateur de ce type de fonctionnalité l'attende ou la veuille d'une manière différente?

Beaucoup de gens n'aiment pas la granularité ridicule du nombre d'importations dont vous avez besoin pour faire quoi que ce soit dans Python-land, je ne pense pas que quiconque propose d'aller aussi loin.

Mais il y a un argument important selon lequel un grand nombre de dépendances et le gonflement des bibliothèques ne sont pas souhaitables pour certains cas d'utilisation, Julia le langage ne devrait vraiment pas nécessiter l'installation de bibliothèques d'exécution Fortran sur votre ordinateur, mais pour le moment, la bibliothèque standard Julia le fait, que ce soit ou non le code que vous voulez exécuter fait n'importe quelle algèbre linéaire (ou fonctions de Bessel, ou FFT, etc.). Tout cela est bien couvert par # 5155 et d'autres problèmes cependant - «installé par défaut» et «minimum nécessaire pour toute fonctionnalité» devraient finalement être séparés en différents ensembles de modules.

Merci Tony, j'appuie cela. De plus, avec notre système de paquets, ce n'est vraiment pas un problème d'avoir des "boîtes à outils" comme des paquets qui en regroupent plusieurs. Avec la macro @reexport cela fonctionne bien.

Mais il y a un autre point. Dans un package, il est beaucoup plus facile de faire avancer une idée. Si l'on veut changer quelque chose, on le fait simplement. Au sein de la base un est beaucoup plus restreint.

Il semble que si un patchwork de packages permet une plus grande indépendance, cela crée également une plate-forme fragmentée qui est plus difficile à naviguer. Il semble que l'utilisation d'une structure plus unifiée et généralisée rationaliserait et faciliterait l'interaction interdisciplinaire et l'exploration de nouveaux domaines.

Quelle est actuellement la manière la plus idiomatique de faire une matrice vectorielle *?
Par exemple, disons que j'ai X avec la forme (K, N) et b avec la forme (K,) , et je veux multiplier par b sur le gauche pour obtenir un vecteur de longueur (N,) .
Dois-je appeler BLAS.gemv('T',1.0,X,b)
Ou reshape(b'*X,size(x,2))
Les deux sont un peu moche.

Je suppose que tu pourrais faire X'b

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

Quelle est actuellement la manière la plus idiomatique de faire une matrice vectorielle *?
Par exemple, disons que j'ai X avec forme (K, N) et b avec forme (K,), et je veux
multiplier par b à gauche pour obtenir un vecteur de longueur (N,).
Dois-je appeler BLAS.gemv ('T', 1.0, X, b)
Ou remodeler (b '* X, taille (x, 2))
Les deux sont un peu moche.

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -79405868.

Ouais, j'avais pensé que cela déclencherait une copie de X.
Mais @time semble indiquer qu'il n'y a pas de copie, en fonction de l'allocation de mémoire

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)

Nous faisons une analyse sophistiquée de 'donc X'y se termine par Ac_mul_B(X,y) et rend le
même appel BLAS que vous avez suggéré.

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

Ouais, même si j'avais pensé que cela déclencherait une copie de X.
(Mais @time https://github.com/time semble indiquer qu'il n'y a pas
copie, basée sur l'allocation de mémoire

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

julia> @time y'X;
temps écoulé: 0,00177384 secondes (15 ko alloués)

julia> @time X'y;
temps écoulé: 0,000528808 secondes (7 ko alloués)

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -79421713.

Croyez-le ou non, un résumé de tout ce fil se rapproche de sa matérialisation.

Un dernier point: quelqu'un a-t-il pensé à cela. pourrait en fait être un animal totalement différent de '? Ce dernier a la bonne structure pour être duel, mais. pas si le champ sous-jacent n'est pas les nombres réels. Est-ce fou d'attribuer? la notion «inverser tous les indices» de transposer et réserver toutes les notions de dualité à ', ce qui produit un «vecteur ligne drôle» / conjugué hermitien?

Je pense que dans tous les cas conj(transpose(x)) devrait être équivalent à ctranspose(x) .

Comme indiqué ci-dessus, j'espère qu'aucun des types de wrapper créés par transpose et ctranspose recevra un nom contenant un concept mathématique spécifique tel que dual . Un langage scientifique tel que Julia devrait fournir un ensemble de structures de données utiles et devrait implémenter des opérations communes, mais je pense que ce serait une erreur de lui associer une structure mathématique stricte, car ce ne sera pas approprié pour certaines applications et trop complexe pour autres. Il appartient à l'utilisateur d'utiliser ces structures de données et ces opérations pour implémenter les opérations mathématiques dans son application. Il existe certainement des applications pour z.' * A * z retournant un scalaire où z est un vecteur complexe et A une matrice, même si cela n'a rien à voir avec un produit interne et / ou double vecteurs.

@jutho pourriez-vous donner un cas d'utilisation pour z.' * A * z ?

Si z1 et z2 sont la représentation de deux vecteurs complexes Z1 et Z2 (par exemple des vecteurs tangents dans la partie holomorphe de l'espace tangent d'une variété de Kähler ) et a est la représentation matricielle d'un tenseur A avec deux indices covariants (par exemple une forme complexe (2,0) de la variété de Kähler) puis A(Z1,Z2) = z.' * a * z .

Notez que je souligne ici que les objets julia z1 , z2 et a ne forment qu'une _représentation _ de certains objets mathématiques (par rapport à une base / coordination choisie) et donc les opérations sur les structures de données ne peuvent pas être associées de manière unique à des opérations mathématiques sans savoir ce que ces structures de données représentent.

@jutho merci. Votre argument concernant les représentations est bien compris et a été présenté à maintes reprises dans cette discussion. J'essaie de trouver l'interface minimale des vecteurs et des matrices, et de voir si cette interface minimale est de toute façon fondamentalement incompatible avec l'interface minimale des tableaux, et ce que nous pouvons décharger vers des types de données abstraits définis par l'utilisateur.

À ce stade, je suis totalement en faveur de la proposition @StefanKarpinski , également principalement reprise par @andyferris ci-dessus. En particulier

  1. Indexation de style APL
  2. v 'donne une sorte de type Covector ou Transpose

Tout le reste est un détail. Il peut être intéressant d'ajouter les fonctions row(M,i) et col(M,i) . Sinon, pour extraire une ligne en tant que covecteur, je suppose que vous auriez besoin de M[i,:].' ? IIUC, M[i,:]' ferait un conj non voulu dans ce cas?

Oui, je dois ajouter que je n'ai pas pleinement apprécié la gentillesse de l'indexation de style APL la dernière fois que j'ai écrit, mais maintenant je suis pleinement en faveur de cette modification. Cela rend à son tour le truc à double vecteur encore plus convaincant, et les fonctions row / col .

Depuis, j'ai essayé de jouer avec une implémentation et le plus déroutant était de savoir si le covecteur extrait d'une matrice était conjugué ou non ...

Je pense que vous voulez prendre les valeurs littérales de la matrice. En utilisant la notation Dirac, développez (toute) matrice M = sum_i | i>est par exemple [0,0,1, ..., 0], nous voulons extraireU nous obtenons row(U,i)' = col(U’,i) ce qui est parfaitement logique pour extraire les vecteurs propres gauche et droit d'une composition propre.

Andy

Le 15 mars 2015, à 21h36, Jeff Bezanson [email protected] a écrit:

À ce stade, je suis totalement en faveur de la proposition @StefanKarpinski https://github.com/StefanKarpinski , également principalement reprise par @andyferris https://github.com/andyferris ci-dessus. En particulier

Indexation de style APL
v 'donne une sorte de type Covector ou Transpose
Tout le reste est un détail. Il pourrait être intéressant d'ajouter des fonctions row (M, i) et col (M, i). Sinon, pour extraire une ligne en tant que covecteur, je suppose que vous auriez besoin de M [i ,:]. ' ? IIUC, M [i ,:] 'ferait une conjonction non voulue dans ce cas?

-
Répondez directement à cet e-mail ou consultez-le sur GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -81228816.

Des volontaires pour commencer à travailler là-dessus? par exemple @mbauman , @jakebolewski que je suis surpris de voir ne sont pas déjà dans ce fil :)

Trouver tout ce qui doit changer peut être fastidieux, mais le changement de base du comportement d'indexation ne devrait pas être trop grave. Probablement @jiahao et @andreasnoack peuvent en dire plus sur la façon dont les Covectors devraient être incorporés, par exemple quel devrait être leur supertype.

Nous avons besoin de 9, pas 8 autres commentaires avant de pouvoir aller de l'avant avec cela.

Je peux vous aider.

nous sommes assez proches

Comme commentaire pertinent, s'il y a un type de wrapper Transpose et CTranspose , devrait-il également être un type de wrapper Conjugate , tel que conj(A) aussi paresseux. Pour multiplier les matrices avec BLAS, ce n'est pas vraiment utile car il n'y a pas de support spécial pour cela (et C dans BLAS signifie conjugué hermitien), mais s'il y a jamais une implémentation complète de Julia BLAS, ce serait bien de prendre également en charge conj(A)*B sans conjugaison explicite.

Pour ma part, j'ai l'impression de prendre maintenant les transpositions vectorielles beaucoup plus au sérieux qu'auparavant.

Peut-être que @andreasnoack et @simonbyrne peuvent nous dire si # 6837 doit être revisité.

Je suis d'accord avec @simonbyrne que nous ne devrions pas avoir Transpose{Array} <: AbstractArray .

Autres réflexions générales:

  • J'ai réalisé que le calcul du produit externe contient actuellement une exception à la règle "n'ajoutez pas automatiquement les dimensions de singleton de fin". Si u a la taille (n,) , u * u' est un calcul impliquant (n,) x (1, n) . Ce produit ne peut pas être calculé en utilisant les règles ordinaires de multiplication matricielle _ à moins_ que nous remodelions automatiquement le premier argument en (n, 1) .
  • La règle "ajouter automatiquement les dimensions de singleton de fin" de la sémantique d'indexation MATLAB est fondamentalement incompatible avec la règle "transposer inverse toutes les dimensions". Avec l'ancienne règle, les tableaux de forme (n,) sont sémantiquement identiques aux tableaux remodelés de forme (n,1) et de forme (n,1,1) , etc. Mais notez que si transposer inverse toutes les dimensions, alors le les tableaux résultants ont la forme (n,) , (1, n) et (1,1,n) , qui _ne peuvent pas_ être équivalents si vous êtes seulement autorisé à ajouter des singletons de fin. En poussant cela à l'extrême logique, la transposition d'un tableau peut avoir un nombre arbitraire de singletons _leading_ et a donc une forme ambiguë, qui est logiquement incohérente.

J'ai également fait une étude de la littérature et découvert une histoire intéressante de l'APL. Ce que nous avons appelé la règle d'indexation APL ne figurait pas dans le livre d'Iverson de 1962, mais existait dans APL \ 360 (1968; la première implémentation d'APL). Cependant, APL \ 360 a confondu scalaires et 1-vecteurs, une incohérence qui n'a pas été reconnue dans la littérature jusqu'à (Haegi, 1976). Une discussion sur la sémantique formelle des tableaux multidimensionnels est apparue pour la première fois dans la thèse de doctorat de Brown (1972; plus tard, il a conçu APL2), et a suscité une ligne de travail formalisant leur sémantique.

Une excellente étude des développements menant à APL2 est:

  • Karl Fritz Ruehr. "Une enquête sur les extensions à APL." In Proceedings of the International Conference on APL, APL '82, pages 277–314, New York, NY, USA, 1982. ACM.

Notables dans la littérature pour l'attention portée aux règles d'indexation:

  • T. Plus. "Axiomes et théorèmes pour une théorie des tableaux." IBM Journal of Research and Development, 17 (mars): 135–175, 1973.

    • Un tome géant formalisant la sémantique des tableaux multidimensionnels en utilisant la théorie des ensembles axiomatiques de Quine, montrant comment les scalaires peuvent être confondus avec les tableaux de rang 0 avec la notion d'ensembles autonomes pour construire ce que la littérature APL appelle des «tableaux flottants». ([1] == 1, en contraste avec les «tableaux fondés» où [1]! = 1. Les travaux ultérieurs de Sheila M. Singleton dans sa thèse de maîtrise de 1980 ont montré que la théorie des tableaux de More pouvait également être adaptée pour décrire les tableaux ancrés.)

    • More mentionne également le produit interne renvoyant un scalaire comme règle importante pour guider la sémantique des tableaux.

    • More fait également allusion à la complexité de la gestion des "up / down-ness" pour les tableaux multidimensionnels généraux:

      "Soit V un espace vectoriel à n dimensions sur un champ. Sans tenir compte des considérations de contravariance et de covariance, un tenseur de valence q sur V est une application multilinéaire du produit cartésien de la liste VV ... V de longueur q en un vecteur Si V a une base, alors un tenseur de valence q sur V peut être représenté par un _tenseur de composant_, qui est un tableau sur q axes, chacun de longueur n. "

  • G. Lewis. "Un nouveau système d'indexation de tableau pour APL", Actes de la septième conférence internationale sur APL - APL '75, 234-239, 1975.

    • Cet article a été le premier à préconiser le tuple d'index dans une opération d'indexation comme un objet de première classe dans APL, et a noté qu'une construction cohérente du résultat de l'indexation peut être obtenue par des produits cartésiens systématiques le long de chaque rang du tuple d'index.

  • Hans R Haegi. "L'extension d'APL aux structures de données arborescentes." ACM SIGAPL APL Quote Quad, 7 (2): 8-18, 1976.

    • Cet article se plaignait d'une incohérence dans APL \ 360 classique, qui confondait scalaires et 1-arrays, et a soutenu que la règle d'indexation APL exigeait que cette fusion ne soit pas valable.

    • Cet article contient également une construction très similaire à Lewis, 1975; l'œuvre semble indépendante.

  • JA Gerth et DL Orth. "Indexation et fusion dans APL." Dans Proceedings of the International Conference on APL, APL '88, pages 156–161, New York, NY, USA, 1988. ACM.

    • Noté que la règle d'indexation APL peut être justifiée en considérant l'indexation comme une opération de diffusion mappant l'ensemble d'index à l'ensemble de valeurs. Cette interprétation fonctionnelle suggère naturellement la règle de préservation des rangs et la construction cartésienne de Lewis et Haegi.

De plus, sans la règle "peut ajouter des dimensions de singleton de fin", nous n'obéissons pas à l'identité

image

puisque le côté gauche est un scalaire (par définition de trace) et le côté droit a la forme (1, n) x (n, n) x (n,) = (1,) . Inversement, nous pouvons prendre cette identité comme principe directeur pour sélectionner la sémantique de transposition vectorielle. Le point principal est la propriété cyclique de l'opération de trace qui définit la première identité. La quantité à l'intérieur de la trace doit être soit un scalaire, soit une matrice (la trace d'un vecteur n'est pas définie). Avv' est déjà sans ambiguïté une matrice: (Av)v' et A(vv') produisent le même résultat. Mais aussi à partir de la deuxième quantité, v'Av doit être une matrice _ou_ un scalaire. (Si scalaire, la deuxième identité tient également.) v'Av ne peut être un vecteur 1 que si la règle "peut ajouter des dimensions de singleton de fin" est active, auquel cas elle peut être transformée de manière transparente en une matrice 1x1.

Ainsi si nous voulons que la trace soit définie, alors elle impose nécessairement des restrictions sur les formes autorisées des produits extérieurs vv' et des formes quadratiques v'Av .

@jihao : Je ne suis pas tout à fait sûr de ce que vous plaidez pour notre contre. Pouvez-vous énoncer un peu plus clairement la règle «peut ajouter des dimensions de singleton de fin»? Quand s'applique-t-il? Pensez-vous que nous devrions le soutenir?

Je pense que certains de vos arguments peuvent être pris pour renforcer la position selon laquelle nous ne pouvons pas considérer la transposition comme une inversion de toutes les dimensions (un vecteur colonne serait transposé dans un vecteur colonne, ou éventuellement un tableau avec un nombre arbitraire de dimensions singleton principales). Pour rester cohérent avec l'algèbre matricielle, je pense que cela doit être considéré comme un échange des deux premières dimensions à la place. Ensuite, je crois que certaines des contradictions que vous dites disparaissent. Espérons que le reste disparaîtra si nous n'ajoutons aucune dimension singleton lors de la transposition (la première dimension absente de covector ne compte pas).

Mes commentaires généraux ci-dessus ne sont pas pour préconiser ces règles, mais plutôt pour aider à délimiter l'espace de conception des règles d'indexation de tableau et leur interaction avec les identités d'algèbre linéaire.

La règle «peut ajouter des dimensions de singleton de fin» est utilisée par MATLAB et (selon @alanedelman) a été introduite pour prendre en charge les tableaux multidimensionnels. Le calcul de l'index à l'offset utilisé dans les tableaux MATLAB est défini par sub2ind , qui renvoie le même index linéaire quel que soit le nombre de 1 à la fin que vous lui lancez. De plus, la documentation d'indexation matricielle de MATLAB indique que pour les opérations d'indexation:

Le nombre d'indices spécifié pour B, sans compter les indices de fin égal à 1, ne dépasse pas ndims (B).

Plus formellement, je pense que cela peut être déclaré comme suit:

Pour les opérations prenant des tableaux en entrée, (n,) -arrays, (n,1) -arrays, (n,1,1...) tableaux sont tous dans la même classe d'équivalence en ce qui concerne les arguments valides pour ces opérations. (Si n=1 , alors la classe d'équivalence comprend également des scalaires.)

Exemples:

  • A*b et A\bA est une matrice et b peut être un vecteur ou une matrice (sémantique identique pour les matrices n x 1 ),
  • hcat(A, b)A est une matrice et b peut être un vecteur ou une matrice

Pour la plupart, Julia n'a pas cette règle «peut ajouter des dimensions de singleton de fin», à l'exception de l'exemple de produit externe. Il y en a peut-être d'autres, mais je ne peux pas y penser pour le moment.

Tant que Transpose{A<:AbstractArray} n'est pas un sous-type de AbstractArray je pense que je ne vois pas de problème (mais j'oublie probablement quelque chose car je n'y ai pas réfléchi autant que vous) :
avec

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

(et de même pour CTranspose ) nous pouvons avoir

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

La seule question ouverte est de savoir si AbstractVector * AbstractTMatrix doit être pris en charge lorsque AbstractTMatrix a 1 comme première taille, ou si AbstractVector * AbstractVectorTranspose est suffisant.

De plus, le nouveau système de types pourrait aider à exprimer un peu plus soigneusement certains de ces typealias et union sa.

De plus, si par exemple v.'*A est calculé comme (A.'*v).' , alors le besoin d'un wrapper Conjugate apparaît si A lui-même est par exemple A=B' .

Je suis d'accord avec @simonbyrne que nous ne devrions pas avoir Transpose{Array} <: AbstractArray .

Pouvez-vous nous en dire plus? Je pensais que l'opinion dans https://github.com/JuliaLang/julia/issues/4774#issuecomment -59428215 était que CoVector ne devrait pas être un sous-type de AbstractVector, mais cela me semblerait un peu étrange de ne pas avoir Transpose{Matrix} <: AbstractArray .

Pour ce que ça vaut, je pense que CoVector devrait se comporter principalement comme Vector sauf que Vector convertit en Matrix tant que matrice de colonnes tandis que CoVector convertit en Matrix tant que matrice de lignes.

Je suppose que cela signifierait que l'indexation dans un covector devrait fonctionner de la même manière que dans une matrice de lignes?

C'est une pensée intéressante. Les choses deviennent-elles plus faciles ou plus compliquées si seules les dimensions de singleton _leading_ sont supprimées dans les types transpose / covector?

(J'ai suivi ce problème avec intérêt mais mon algèbre linéaire est suffisamment rouillée pour que je ne me sente pas qualifié pour y contribuer).

@mbauman :

Les choses deviennent-elles plus faciles ou plus compliquées si seules les dimensions singleton principales sont supprimées dans les types transposés / covecteurs?

Si vous autorisez un nombre arbitraire de dimensions singleton principales, alors les index de tableau ne sont plus bien ordonnés et il n'y a pas de "la" première dimension, ce qui est assez bizarre pour que nous puissions aussi bien prendre le bon ordre comme un axiome.

@tkelman :

Je pensais que l'opinion dans # 4774 (commentaire) était que CoVector ne devrait pas être un sous-type de AbstractVector, mais il me semblerait un peu étrange de ne pas avoir Transpose {Matrix} <: AbstractArray.

Il est devenu clair pour moi que ce problème concerne entièrement la séparation de la sémantique des tableaux (cf # 10064) de la sémantique de l'algèbre linéaire, et la recherche des endroits où se mélangent.

  • La sémantique des tableaux est définie par des fonctions telles que size, length, getindex, setindex, hcat, vcat, reshape, rot90 ...
  • La sémantique de l'algèbre linéaire est définie par des fonctions comme +, -, *, /,, ', trace ...

Si nous définissons les fonctions cat comme faisant partie de l'interface essentielle d'un AbstractArray , alors Transpose{<:AbstractArray} n'est clairement pas un AbstractArray car leurs comportements de concaténation sont différents . Si l'on considère que seules la forme et l'indexation font partie de l'interface essentielle, alors la situation est moins claire.

Si nous avons besoin d'une concaténation dans le cadre de l'interface essentielle d'un AbstractArray , il est également plus facile de justifier pourquoi des types comme SymTridiagonal ne sont pas AbstractArray s, puisque les opérations de concaténation sur SymTridiagonal s comme [SymTridiagonal(randn(5), randn(4)) randn(5)] sont actuellement pas définis.

@toivoh :

Je suppose que cela signifierait que l'indexation dans un covector devrait fonctionner de la même manière que dans une matrice de lignes?

Il existe des identités qui suggèrent que les comportements d'indexation de Transpose{Vector} devraient être les mêmes que ceux des Vector s ordinaires. Considérez que pour les types numériques, v[1] produit le même résultat que v' * e₁ = v ⋅ e₁ et v[1:2] produit le même résultat que v' * [e₁ e₂] , où e₁ est la base canonique Vector{Int} [1, 0, 0, ...] et e₂ est [0, 1, 0, 0, ...] . Si nous exigeons que ces identités relatives à l'indexation, aux produits internes et à la transposition tiennent, alors on pourrait prétendre que

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

(où la première étape est un nouvel axiome et la quatrième utilise le fait que la transposition d'un scalaire est un no-op) de sorte que l'indexation dans Transpose{Vector} ignorerait essentiellement la transposition, et l'indexation dans CTranspose{Vector} conjuguerait les éléments indexés.

moi que ce problème concerne entièrement la séparation de la sémantique des tableaux (cf # 10064) de la sémantique de l'algèbre linéaire, et la recherche des endroits où se mélangent.

  • La sémantique des tableaux est définie par des fonctions telles que size, length, getindex, setindex, hcat, vcat, reshape, rot90 ...
  • La sémantique de l'algèbre linéaire est définie par des fonctions comme +, -, *, /,, ', trace ...

+1 à ce point de vue et ne pas avoir Transpose <: AbstractArray . De plus, lors de l'indexation d'un covecteur, il doit être avec un seul index, car sinon le résultat de covector * vector (se contracter sur un seul index) ne pourrait pas donner un scalaire (un objet avec des indices nuls).

@jihao : Je ne suis pas sûr de voir pourquoi nous avons besoin ou voulons

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

comme un nouvel axiome. Même si un covecteur était indexé en tant que matrice de lignes, je pense que nous obtiendrions le même résultat pour ce qui précède en raison de l'indexation linéaire.

Et +1 pour voir les covecteurs comme concernés par l'algèbre linéaire.

Mais il n'y a aucune raison pour que la concaténation avec un SymTridiagonal ne soit pas définie, non?

L' indexation linéaire

Je pensais que l'indexation linéaire concernait l'ordre de stockage, et il n'y a vraiment pas d'autre ordre raisonnable pour l'indexation linéaire d'un vecteur de toute façon, non? (Et désolé pour la faute d'orthographe.)

Il n'y a pas d'ordre de parcours sensible unique en mémoire. Même pour les tableaux Fortran, vous pouvez choisir de stocker les éléments dans la colonne principale, la ligne principale ou même dans l'ordre principal de la colonne inversée (ce qui est exactement ce que faisait le compilateur IBM Fortran I d'origine). De plus, il existe d'autres structures de données (voir # 10064) comme les essais qui peuvent être utilisées comme tableaux et ont encore plus d'options pour l'ordre de traversée.

On pourrait dire la même chose des vecteurs et des matrices. Puisque l'indexation linéaire accède aux éléments dans le même ordre pour une matrice de colonne et sa transposition (et le même que pour un vecteur colonne), pourquoi un covecteur devrait-il être différent? Si cela devait être différent, je pense que ce devrait être que nous ne définirions pas du tout l'indexation pour les covecteurs.

@toivoh oui, les mêmes définitions

L'indexation des objets Transpose peut être interdite. Je ne dis pas qu'ils doivent être indexables, mais s'ils le sont, ils ne doivent pas nécessairement avoir le même comportement d'indexation. Pour être prudent, nous pouvons laisser l'indexation indéfinie pour le moment et voir si des cas d'utilisation se présentent.

De nombreuses implémentations pures de Julia de fonctions d'algèbre linéaire voudront indexer dans une Transpose, non? L'écriture de la multiplication de matrice de Julia pure (pour les types de nombres non BLAS) devient facile si vous n'avez pas à faire la distinction entre les cas possibles (normal, transpose, ctranspose, conj?) Pour les deux matrices impliquées, mais les traitent simplement comme des matrices ordinaires. Des méthodes inconscientes du cache pourraient être essayées pour obtenir un modèle d'accès mémoire quelque peu local.

D'accord, duh.

@Jutho : Je suis d'accord. Et pour y entrer, les covecteurs devraient indexer comme des matrices de lignes, non?

@toivoh , si vous voulez dire qu'ils devraient avoir un index supplémentaire 1 devant, je ne suis pas d'accord et je ne vois pas comment cela est impliqué par ma déclaration. Je ne parlais que des produits matriciels matriciels. Matrix * vector ou covector * matrix sont des méthodes différentes qui nécessitent des définitions de fonction différentes, non seulement parce qu'elles ont un modèle d'accès à la mémoire différent, mais aussi parce qu'elles ont un type de retour différent (Matrix_vector = vector ou covector_matrix = covector), donc il y a un raison très pratique en Julia de ne pas mélanger ces choses.

En général, je ne suis pas un grand fan de la possibilité d'ajouter des indices supplémentaires 1 lors de l'indexation d'un tableau à N dimensions, ou par exemple de l'alias de type VecOrMat . Cela permet une programmation bâclée, mais c'est aussi pourquoi cela facilite les erreurs ou la détection d'autres erreurs plus lentement. Je ne vois que deux façons utiles d'exécuter un tableau à N dimensions, c'est-à-dire avec des indices N exacts, au cas où vous l'utiliseriez comme un objet multilinéaire, ou avec un index linéaire, lorsque vous le traitez comme un vecteur dans un produit tensoriel espace (par exemple pour ajouter deux tableaux de ce type ou le multiplier par un scalaire). Bien que cela soit suffisant pour mon utilisation, je peux accepter que cela soit trop limité pour les autres.

@Jutho : Ok, je suis d'accord que cela n'a probablement pas d'importance puisque le type de retour doit être différent de toute façon.

Voici une tentative pour décrire ce que nous essayons de faire et donner quelques axiomes:

Je pense que nous sommes arrivés à un consensus assez clair sur le fait que le point de départ est l'algèbre matricielle (qui est basée uniquement sur des matrices). Pour la plupart des opérations, nous savons comment elles se comportent dans le cadre de la matrice pure.

Je crois que ce que nous essayons de faire est d'étendre et d'affiner le paramètre de matrice pure de manière cohérente pour avoir également de vrais scalaires et vecteurs, et parce que cela semble être nécessaire pour la cohérence, les covecteurs.

Voici ma vision des scalaires et des vecteurs (comme vu du point de vue de la matrice pure): Un scalaire est une matrice qui, par son type, est contrainte d'être 1 x 1. Un vecteur est une matrice qui, par son type, est contrainte à be nx 1. (Je soutiendrai ci-dessous qu'un covecteur est une matrice qui, par son type, est contrainte d'être 1 x n.) De ce point de vue, nous pouvons donner deux axiomes: (pas très formellement décrit ci-dessous)

  • Extension: Considérons une fonction des matrices aux matrices. S'il produira toujours une matrice de sortie avec une taille de 1 dans une dimension donnée, étant donné les entrées de certains types, ce fait sera encodé dans le type de sortie (ce qui en fait un scalaire / vecteur / covecteur).
  • Raffinement: Si une fonction qui prendrait un argument de matrice dans le paramètre de matrice pure nécessite que l'entrée ait une taille de un dans une ou plusieurs dimensions, elle peut refuser d'accepter une entrée lorsque ce fait n'est pas codé dans le type.

Si nous sommes d'accord avec ce qui précède, la transposée d'un vecteur doit être le type de covecteur décrit ci-dessus: La transposition d'une matrice anx 1 donne une matrice 1 xn. Si nous encodons le fait que la taille le long des premières dimensions du résultat est toujours un, nous obtenons le covecteur comme décrit ci-dessus.

Si vous partez du point de vue de l'algèbre matricielle, alors ce que vous dites est correct. C'est le modèle que MATlab implémente probablement assez parfaitement. Tout est une matrice. C'est un système fermé, toutes les opérations sur les matrices produisent de nouvelles matrices.

J'ai certainement eu l'impression que le but de ce problème (prendre au sérieux les transpositions vectorielles, car ce ne sont pas que des matrices) était de s'éloigner de ce modèle d'algèbre matricielle, car il commence à montrer des incohérences si vous voulez séparer les nombres des matrices 1x1 pour des raisons d'efficacité. L'alternative est alors de suivre le modèle de l'algèbre linéaire , où il y a une distinction claire entre le champ (scalaires), l'espace vectoriel (et son dual correspondant), et l'espace des opérateurs / transformations linéaires (matrices).

Je pense que les opérations d'algèbre linéaire dans Julia sont assez fortement enracinées dans la tradition matlab, avec quelques exceptions notables comme avoir des scalaires et des vecteurs, et ne pas essayer de deviner l'utilisateur. Tout ce qui s'éloigne trop de cela est probablement une très grosse perturbation.

Je pense que mes axiomes ci-dessus devraient aller de pair pour résoudre les incohérences qui apparaissent lorsque vous souhaitez séparer les scalaires et les vecteurs des matrices (pour des raisons d'efficacité et d'exactitude). Mais je suis vraiment ouvert à entendre parler d'autres systèmes possibles.

Je suis d'accord avec @Jutho ici; le but de ce problème est de s'éloigner de la sémantique «tout est une matrice» de MATLAB. La règle "peut ajouter des dimensions singleton de fin" de MATLAB est nécessaire pour que l'univers soit fermé sous des opérations d'algèbre linéaire, mais cette règle définit des classes d'équivalence contenant des membres de type T et d'autres de type Array{T,N} pour all N , et c'est la principale raison pour laquelle le système de types de MATLAB est indécidable. (Voir le théorème 1 de Joisha et Banerjee, 2006 - bien que le résultat soit énoncé en termes de formes, le problème est vraiment de savoir comment changer le rang de tableau peut changer la sémantique du programme.)

Mais je pense toujours que nous avons eu un assez bon consensus sur le fait que la multiplication des scalaires, des vecteurs, des covecteurs et des matrices devrait être associative (à l'exception de choses comme (v'*v)*v où deux non-scalaires se multiplient pour produire un scalaire), et que par exemple v'*M*v devrait produire un scalaire, M*v un vecteur et v'*M un covecteur. Je ne sais pas dans quelle mesure il est possible de s'écarter de la sémantique de tout-est-une-matrice tout en remplissant ces conditions.
Qu'est-ce que nous essayons d'éviter de plus, et quelles propriétés voudrions-nous gagner à la place?

À quel point est-ce pire si nous ne confondions que T et Array{T,0} dans certains cas? (Par exemple, l'indexation: M[:,2] produit un Array{T,1} , mais M[2,2] ne produit pas un Array{T,0} )

Nous pouvons toujours maintenir une sémantique valide avec Transpose{Vector} .

J'ai relu tous les commentaires sur l'associativité et j'ai conclu que la plupart de cette discussion ne portait pas réellement sur l'associativité en soi, mais plutôt sur la sémantique des produits internes et des produits externes. Si vous trouvez une expression qui ne concerne ni l'un ni l'autre, veuillez l'indiquer.

Le problème avec la sémantique de type Matlab est que M*v' et v*M fonctionnent parfois même si ce n'est pas le cas. Si M est m x 1 alors M*v' est valide et renvoie une quantité semblable à un produit externe (puisque v' est 1 x n ). De même, si M vaut 1 x m et que nous avons la règle "peut ajouter des singletons de fin", alors v*M peut être évalué comme le produit de n x 1 et 1 x m matrices, ce qui donne à nouveau une quantité externe semblable à un produit.

La question de la fusion de T et de Array{T,0} a également été soulevée dans la littérature APL - dans APL, les tableaux multidimensionnels sont imbriqués récursivement, ce qui soulève la question de savoir si Array{T,0} et T se distinguent. Sinon, ce sont des "tableaux à la masse" (qui récurent jusqu'à T ), sinon ce sont des "tableaux flottants" (qui récurent jusqu'à Array{T,0} seulement). Je crois que More, 1973 a en fait prouvé que l'un ou l'autre choix est axiomatiquement cohérent. Je ne sais pas si APL a jamais résolu la question de savoir lequel utiliser avant que la plupart des pratiquants ne prennent leur retraite ou ne passent à autre chose.

@jiahao : Je n'avais pas réalisé à quel point votre observation était fondamentale

v[i] = e_i' * v

pour lier l'algèbre linéaire et la sémantique d'indexation. Mais alors vous devez aussi considérer

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

ce qui indique qu'un produit interne avec un vecteur de base de la droite correspond à une indexation le long de la deuxième dimension. Ainsi, je maintiendrais que l'entrée i e d'un covecteur v' devrait être indexée comme

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

où bien sûr nous aimerions écrire autre chose que 1 comme premier index, mais quoi?
Quoi qu'il en soit, puisque nous permettons

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

alors 1 devrait être autorisé comme espace réservé également dans le cas ci-dessus.

v' * e_i est un scalaire, donc e_1' * (v' * e_i) est un covecteur, pas un scalaire. Donc, les dimensions ne correspondent pas pour penser à v'[1, i] = e_1' * v' * e_i

edit: Je pense que cela pourrait être un argument contre l'autorisation des singletons de fin dans l'indexation?

Oui, l'indexation matricielle est la prochaine étape logique, et e_i' * M * e_j est en fait l'une des expressions où l'axiome d'associativité devient utile, puisque

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

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

devrait être égal. L'indexation matricielle peut être dérivée d'une règle d'indexation vectorielle et d'une règle d'indexation de covector.

Je pense qu'une résolution cohérente de ce problème peut exiger que des expressions telles que v[i, 1] soient interdites, car la règle qui autorise ce comportement d'indexation
a) devrait provoquer de faux cas pour que A*v' et v*A fonctionnent (le premier fonctionne mais le second ne fonctionne pas car nous appliquons la règle du singleton de fin de manière incohérente), et
b) si nous considérons l'égalité de v[i] = v[i, 1, 1, 1] alors la règle d'indexation des covecteurs correspondante ressemblerait à (v')[1, 1, 1, i] et il faut avoir la règle correspondante "autoriser un nombre arbitraire de singletons principaux" pour les covecteurs pour des raisons de cohérence. Je trouve très dérangeant l'absence d'une première dimension définie de manière unique.

Je ne pense pas que ce raisonnement d'index va vraiment nulle part. L'indexation est une propriété générale des tableaux de N -dimensionnels, et qu'ils peuvent être écrits comme de simples expressions matricielles pour N=1 ou N=2 est plutôt trivial et ne contient aucun élément fondamental information. Mais cela ne se généralise pas aux tableaux de rang plus élevé et ne devrait donc pas être utilisé pour déterminer si un covecteur a besoin d'un premier 1 ou non lors de son indexation. Cela conduit rapidement à des incohérences comme observé dans les articles précédents.

Comme indiqué ci-dessus, je n'ai jamais été fan de me fier aux indices de fin et je ne pouvais pas penser à une seule situation où cela serait nécessaire ou même vraiment utile. Mais je peux accepter que mon point de vue soit trop limité.

Je n'ai pas complètement digéré tout cela mais ma question principale est simplement: est-ce que size(covector) égal à (n,) ou (1,n) ?

S'ils ne font pas partie de la famille AbstractArray , il n'est même pas strictement nécessaire que size soit défini.

Bien que pour des raisons pratiques, je suppose qu'il sera défini (comme pour les nombres, etc.), et mon vote va à (n,) . Du point de vue stockage / conteneur, je dirais qu'il n'y a pas de distinction entre les vecteurs et les covecteurs. Il n'est donc pas non plus nécessaire d'utiliser des covecteurs comme conteneurs et ils n'appartiennent donc pas à la hiérarchie AbstractArray . C'est juste un simple type de wrapper pour exprimer qu'ils se comportent différemment des vecteurs par rapport aux opérations d'algèbre linéaire.

«Tant que l'algèbre et la géométrie ont été séparées, leurs progrès ont été lents et leurs usages limités; mais lorsque ces deux sciences se sont unies, elles se sont prêtées chacune des forces mutuelles et ont marché ensemble vers la perfection. - Joseph Louis Lagrange

J'aurais aimé pouvoir contribuer davantage, mais j'ai pensé simplement dire que je suis en faveur d'un système qui favorise la création d'outils mathématiques sophistiqués employés par les physiciens et les ingénieurs intuitifs et précis. Peut-être, cette ressource du MIT, pourrait être utile ...

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

En fait, en y réfléchissant bien, il est plus juste de dire que

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

Ensuite, pour l'indexation matricielle, nous aurions

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]

Actuellement, cela ne tient pas tout à fait dans Julia, par exemple v[i, :] produit actuellement un tableau 1x1 et non un scalaire. (Mais peut-être que ça ne devrait pas)
L'associativité de la multiplication matricielle dans e_i' * M * e_j correspond à la commutativité du découpage selon différentes dimensions M[:, j][i, :] = M[i, :][:, j] , ce qui semble être une caractéristique souhaitable.
Par la ligne de raisonnement ci-dessus, nous devrions avoir

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

@Jutho : Je pense que ce paradigme de "l'indexation comme découpage répété" se généralise aux tableaux / tenseurs de dimension supérieure: vous appliquez un découpage pour chaque dimension à indexer, dans n'importe quel ordre. Cela correspond à une série de contractions avec des tenseurs de premier ordre correspondant à e_i , etc., également dans n'importe quel ordre (tant que vous gardez une trace des dimensions qui correspondent).

Je pense que ce paradigme de «l'indexation en tant que découpage répété» se généralise aux tableaux / tenseurs de dimension supérieure: vous appliquez un découpage pour chaque dimension à indexer, dans n'importe quel ordre. Cela correspond à une série de contractions avec des tenseurs de premier ordre correspondant à e_i, etc., également dans n'importe quel ordre (tant que vous gardez une trace des dimensions qui correspondent).

Oui, je suis très familier avec les contractions tensorielles, etc. ), mais il n'y a pas d'association unique pour savoir si un index correspond à une contraction avec e_i ou avec e_i' . Cela correspond à décider si l'indice correspondant apparaît dans une position covariante ou contravariante, et il n'y a pas de décision unique. Toutes les combinaisons possibles d'indices supérieur et inférieur ont des applications utiles. Mais contracter avec e_i et e_i' n'est même pas la bonne façon de poser cette question du point de vue mathématique, car, comme je l'ai dit ci-dessus, il n'y a en fait pas de mappage mathématique tel que la transposition d'un vecteur. La transposition est une opération définie pour les cartes linéaires (matrices) et même là, elle ne correspond qu'à la transposition matricielle si vous écrivez également des vecteurs doubles comme vecteurs colonnes. La transposition d'un vecteur est juste une astuce pratique qui a été introduite dans l'algèbre matricielle (où en effet les vecteurs sont des matrices n x 1 ) et cela ne fonctionne que parce que vous êtes dans un espace euclidien où il y a un mappage canonique du (conjugate ) espace vectoriel à l'espace double.

En particulier, si vous voulez les propriétés que vous décrivez ci-dessus, vous devriez avoir que M[i,:] renvoie un objet différent (soit une matrice 1xn ou un covecteur) puis M[:,i] (ce qui devrait être une matrice ou un vecteur nx1 ). Le fait que cela ne se généralise pas proprement aux dimensions supérieures est exactement l'un des principaux points de discussion de ce problème, et il semble que la plupart des gens soient en faveur de l'indexation APL, où les dimensions indexées avec un nombre sont supprimées (c'est-à-dire à la fois M[:,i] et M[i,:] produisent un tableau de rang 1, donc un vecteur). L'indexation est une propriété des tableaux, et c'est ce mélange de comportement d'indexation avec des opérations d'algèbre linéaire qui entraîne toutes les confusions / incohérences en premier lieu. Cela peut être cohérent tant que vous restez dans l'écosystème fermé des objets de rang N=2 , c'est-à-dire que tout est une matrice, aussi des vecteurs et des nombres, et que vous ne considérez jamais des tableaux de dimensions supérieures.

Je viens également de réaliser que M[i,:] devrait produire un covecteur par mon raisonnement ci-dessus, comme vous le dites. Il semble donc qu'avoir des covecteurs soit à un certain niveau fondamentalement incompatible avec l'indexation APL. Si nous favorisons l'indexation APL (et j'aime ça), la question devient de savoir où tracer la ligne entre ce monde et le monde où vivent les covecteurs. (J'espérais qu'il serait possible de concilier les deux, peut-être que le reste d'entre vous avait déjà réalisé qu'il faudrait renoncer à ça?)

Ce conflit n'est peut-être pas si surprenant si vous y réfléchissez:

  • Dans l'indexation APL, seules les dimensions significatives et indexables sont conservées dans le résultat; le reste est évincé.
  • Si nous faisions cela avec v' alors nous aurions v' = conj(v) . Le covecteur peut plutôt être considéré comme le suivi d'une première dimension manquante.

Je suppose qu'un bon début serait de restreindre les covecteurs autant que possible, en définissant simplement la multiplication, l'addition / soustraction et la division gauche sur eux.

L'idée semble être de ne pas les faire <: AbstractArray . Serait-il judicieux de les avoir comme sous-types d'un nouveau type LinearOperator ? (Ce que je sais a déjà fait l'objet de discussions, mais je ne me souviens pas très bien des conclusions.)

Je pense que l'absence d'héritage multiple ou de construction de langage pour les interfaces (qui ont tous deux été en discussion) nécessite que certains concepts ne soient implémentés que par une interface «implicite», c'est-à-dire un ensemble de méthodes à définir. Les itérateurs sont l'un de ces concepts, les opérateurs linéaires en seraient probablement un autre qui doit être spécifié par un ensemble de méthodes plutôt que par une hiérarchie de types. Il serait étrange d'avoir un type abstrait LinearOperator et ensuite de ne pas avoir Matrix pour en être un sous-type.

@toivoh C'est une considération importante, c'est la raison pour laquelle j'ai suggéré de nouvelles fonctions comme row(M,i) et col(M,i) pour extraire le i ème vecteur ou covecteur d'une matrice. Ces fonctions seraient juste pour les tableaux bidimensionnels M et pour les personnes intéressées par l'algèbre matricielle. Cela peut sembler au début un peu moins évident que l'indexation de style MATLAB mais, dans l'ensemble, séparer conceptuellement l'idée d'un vecteur et son double / transposition / covecteur permet de clarifier les choses. Dans le cas de la mécanique quantique, tout un champ est passé à la notation bra-ket de Dirac simplement parce que cette distinction entre vecteur et covecteur est si critique pour les vecteurs complexes, et la notation de Dirac le rend évident. J'espère que Julia le fera aussi, en plus d'être un bon outil pour les tableaux de stockage de plus grande dimension et l'algèbre linéaire de plus haute dimension! (Parce que nous sommes gourmands, non?)

Je dois dire qu'une personne qui vient avec une expérience dans MATLAB et une certaine familiarité avec les matrices essentiellement réelles (mais n'est pas un fanatique d'algèbre linéaire dur) peut ne pas comprendre au début pourquoi ce que certains d'entre nous soutiennent est important, mais je croyez que c'est.

Je l'ai dit plus tôt, mais je vais le répéter: de mon point de vue, nous avons cette liste de priorités, avec des causalités qui descendent:

(1) Les tableaux sont fondamentalement des conteneurs de stockage que tous les utilisateurs de Julia utiliseront, et nous voulons la meilleure sémantique pour cela. Les règles de type APL me semblent en tout cas une très bonne solution. D'un autre côté, les tableaux à deux dimensions minimales de style MATLAB avec des hypothèses avec des indices à une dimension à la fin semblent juste contre nature, déroutants, et comme Jutho l'a dit, ils peuvent même conduire à une programmation bâclée où vous ne suivez pas correctement le dimension de votre tableau. J'irais jusqu'à dire que pour le vecteur v , le code v[i,:] devrait générer une erreur puisque v est unidimensionnel. Les opérations élémentaires comme + et .* sont utiles pour n'importe quelle classe de conteneur, pas seulement dans l'algèbre matricielle ou multilinéaire. Les seuls conteneurs de stockage de concession que les gens font pour les choses ci-dessous sont les points supplémentaires sur .* etc.

(2) La plupart des utilisateurs de Julia, mais pas tous, utiliseront l'algèbre matricielle, nous ajoutons donc du sucre syntaxique pour certaines opérations vectorielles et matricielles, principalement avec le symbole * (mais nous pouvons avoir une division matricielle, etc.). Dans ce système, nous prenons des tableaux à un et deux dimensions sont des vecteurs de colonne et des matrices, respectivement. Pour un système matriciel entièrement fonctionnel, nous avons également besoin de vecteurs de lignes (covecteurs) et d'une opération de transposition ' . Un vecteur de ligne est dans tous les sens possibles un tableau unidimensionnel, et j'affirme qu'il devrait être indexé comme tel (et certainement pas comme covec[1,i] !!) Avec les règles APL, nous sommes obligés d'en créer distinction entre les vecteurs et les covecteurs, et le système de types est idéal pour cela (rappelez-vous que nous avons eu la chance de pouvoir déjà réutiliser la matrice et le vecteur comme des typealias de tableaux à une et deux dimensions plutôt qu'un type wrapper ... en principe, nous pourrions enveloppez-les aussi, mais je ne vois pas l'intérêt). Avec le système de types, le compilateur peut comprendre que CoVector * Vector est scalaire et Vector * CoVector est une matrice, et ainsi de suite. Comme toivoh l'a dit, _à partir d'un point de vue MATLAB, le CoVector garde exactement la trace de la première dimension "manquante". Les utilisateurs occasionnels n'auront pas besoin de construire ces objets; ils entreront simplement des vecteurs et des matrices et utiliseront les opérations * et ' . Les gens qui se soucient remarqueront et apprécieront la distinction. Le plus gros changement pour les utilisateurs est la nécessité de changer M[i,:] pour utiliser une nouvelle fonction row(M,i) ou de convertir en classe wrapper avec quelque chose comme Transpose(M[i,:]) ou M[i,:]' - on peut considérer cela comme un avantage, car comme la notation Dirac, on n'oublie jamais quels objets sont des vecteurs et lesquels sont des covecteurs, et l'assignation à des objets avec des assertions de type soulèvera des erreurs le cas échéant. Je pense que cela vaut la peine de débattre de cette notation. À l'avenir, nous pourrions même étendre le système de wrappers pour permettre une évaluation retardée efficace. C'est gagnant-gagnant pour tout le monde, d'après ce que je peux voir.

(3) Certains d'entre nous s'intéressent à l'algèbre multi-linéaire, et ont dû apprendre la différence entre un espace dual et la transposition, etc. Jutho a abordé ce sujet. Les tableaux de Julia sont déjà d'excellents périphériques de stockage pour les tenseurs de plus grande dimension, et des packages supplémentaires (qui peuvent ou non trouver un chemin dans Base) seront utilisés par des gens comme nous. Nous n'avons pas besoin, ni ne voulons, d'opérations comme ' ou * définies sur des tableaux de dimensionnalité supérieure à deux. Je ne vois pas de cas d'utilisation orienté stockage pour ' qui ne peut pas être fait plus proprement en réorganisant explicitement les index. L'idée même de traîner les indices unidimensionnels ne fait que rendre le concept moins attrayant ... alors gardez ' pour les tableaux à une et deux dimensions uniquement - comme MATLAB :) (voir, j'ai de belles choses à dire à propos de MATLAB ...)

Pour un système matriciel entièrement fonctionnel, nous avons également besoin de vecteurs de lignes (covecteurs) et d'une opération de transposition ».

Cette déclaration va vraiment au cœur du problème. L'indexation, la transposition et les produits * sont tous mélangés. De plus, il n'est pas possible de réconcilier la sémantique complète des vecteurs de ligne avec celle des covecteurs sans introduire davantage une fonction comme row(A, i) pour renvoyer la i e ligne de A comme un covector plutôt qu'un tableau unidimensionnel. Actuellement A[1, :] veut signifier à la fois "prendre la première ligne de A" et "prendre la première tranche le long de la deuxième dimension de A", mais ces notions sont fondamentalement incompatibles dans la proposition de covector.

Un vecteur ligne est dans tous les sens possibles un tableau unidimensionnel, et j'affirme qu'il devrait être indexé en tant que tel.

Je pense que vous êtes un peu trop désinvolte avec cette déclaration. La discussion précédente a établi assez clairement que si vous voulez une sémantique d'algèbre linéaire complète (avec les bons produits et transposés), alors un vecteur ligne ne peut pas avoir le même type qu'un tableau à une dimension. Premièrement, l'indexation dans un covecteur doit renvoyer des valeurs conjuguées complexes, (v')[1] = conj(v[1]) , par souci de cohérence avec le produit interne dot . Deuxièmement, les covecteurs ne sont pas des vecteurs, ce sont des fonctionnelles linéaires qui attendent de produire un produit interne avec un vecteur. Troisièmement, les vecteurs et les covecteurs ont des comportements de concaténation de tableaux différents sous hcat et vcat . Pour toutes ces raisons, un vecteur ligne ne peut pas être "dans tous les sens possibles un tableau unidimensionnel".

Je serais en faveur de se débarrasser des singletons de fin: je ne pense pas avoir jamais vu du code de Julia qui s'en est servi. J'allais à l'origine dire que nous en aurions besoin pour les tableaux à 0 dimension, mais je vois que X[] fonctionne très bien.

Je pense que l'indexation APL, ainsi que la fonction row(M,i) pour les vecteurs de lignes, ont le plus de sens et semblent être un bon compromis. Je ne suis pas sûr de l'indexation des vecteurs de ligne, mais je _n'aime pas_ (v')[1,i] .

L'autre grande décision, que nous n'avons même pas encore abordée, concerne les types. Ma seule découverte après avoir essayé cela plus tôt est qu'il est vraiment difficile d'avoir v' être un AbstractVector , car cela rend l'envoi un désordre.

Deux options possibles:

  1. Nous utilisons des types distincts pour la matrice et le vecteur:

    • Transpose <: AbstractMatrix

    • CoVector <: Any

    • une sorte de transposition pour les objets Factorization .

  2. Nous utilisons le même type pour toutes les transpositions, mais avons X' _pas_ être un AbstractMatrix

    • Transpose <: Any

Pour la conjugaison, on peut soit

une. définir ConjugateTranspose (avec ConjugateCoVector si nous choisissons l'option 1 ci-dessus)

b. utilisez un type de wrapper Conjugate , et imbriquez correctement: nous aurions besoin d'une convention pour savoir si nous utilisons Transpose{Conjugate{T}} ou Conjugate{Transpose{T}} .

J'aime avoir Transpose{Matrix} pas un sous-type de AbstractMatrix . Je pense que l'analogue le plus proche que nous ayons dans la base est un type de matrice spécial comme Symmetric , qui se comporte algébriquement comme une matrice mais pas dans sa sémantique d'indexation. (# 987 a établi que sans héritage multiple ou traits Holy, la hiérarchie des types doit respecter la sémantique du conteneur par rapport à la sémantique algébrique.)

Le problème de la composition de types qui sont essentiellement des "balises sémantiques" est également apparu dans # 8240. Je pense que Transpose{Conjugate{T}} serait préférable, car la conjugaison est une notion qui tombe dans le champ sous-jacent des éléments.

Voici des exemples qui suivent parfois la même règle de singleton de fin que MATLAB, et à d'autres moments non:

  • Les singletons de fin sont autorisés dans les opérations d'indexation. (Comme MATLAB)
julia> (1:5)[5,1,1,1,1,1,1]
5
  • Les tranches de fin ne sont pas autorisées dans les opérations d'indexation. (Contrairement à MATLAB, qui les autorise.)
julia> (1:5)[5,:]
ERROR: BoundsError()
 in getindex at abstractarray.jl:451
  • Pour les tableaux de rang> = 3, des singletons de fin implicites sont ajoutés à une opération d'affectation indexée lorsqu'il y a moins d'index que le rang du tableau et que le dernier index est un scalaire (comme 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
  • Pour les tableaux de rang> = 3, des _slices_ de fin implicites sont ajoutés à une opération d'affectation indexée lorsqu'il y a moins d'index que le rang du tableau et que le dernier index n'est pas scalaire (comme 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
  • Pour les tableaux de rang> = 3, des singletons de fin implicites sont ajoutés à une opération d'indexation lorsqu'il y a moins d'index que le rang du tableau et que le dernier index est un scalaire (comme 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
  • Pour les tableaux de rang r> = 3, une opération d'indexation lorsqu'il y a k <r index que le rang du tableau et que le dernier index est une tranche linéarise implicitement le tableau de rang rk restant. (comme 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
  • Les singletons de fin ne sont pas supprimés dans les opérations d'affectation. (Contrairement à 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 n'ajoute pas automatiquement une dimension de singleton de fin lorsque cela permet une opération valide. (Contrairement à Matlab, qui le fait)
julia> 1/[1.0,] #In MATLAB, interpreted as the inverse of a 1x1 matrix
ERROR: `/` has no method matching /(::Int64, ::Array{Float64,1})
  • Les produits extérieurs fonctionnent et sont une exception à la règle précédente; leur sémantique utilise implicitement un singleton final dans le premier argument. (Comme 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

Il semble que ces choses pourraient mériter un peu de nettoyage, une fois que nous aurons conclu comment nous voudrions qu'elles se comportent. Le comportement actuel lors de l'indexation avec trop peu d'indices où le dernier est une tranche semble particulièrement louche.

Sensationnel. Les exemples que Jiahao donne sont extraordinairement dérangeants ... Je n'aime pas traîner les singletons dans les opérations d'indexation et les opérations d'assignation d'indexation en raison de leur implicite et de leur ambiguïté. Si vous n'êtes pas au courant de ces comportements à l'avance, vous pourriez finir par faire une chose alors que vous essayez d'en faire une autre. Je suis favorable à un usage précis et clair de la langue et à éviter les raccourcis ambigus.

Pour implémenter cela, nous allons avoir besoin d'une sorte de répartition triangulaire, sinon je ne sais pas comment vous exprimeriez des choses comme "une matrice avec des entrées Complex64 ou Complex128 , sa transposition , ou sa transposée conjuguée ". Surtout si nous utilisons les options 2 + b ci-dessus.

@ esd100 , lesquels sont ambigus ou dangereux? Ils sont tous soit pratiques - lorsque des "singletons virtuels" sont imaginés pour vous et que vous les vouliez - ou peu pratiques - lorsque vous les vouliez et ils ne l'étaient pas. Aucun de ceux-ci n'a deux significations plausibles avec des comportements différents.

Un vecteur ligne est dans tous les sens possibles un tableau unidimensionnel, et j'affirme qu'il devrait être indexé en tant que tel.

Je pense que vous êtes un peu trop désinvolte avec cette déclaration.

Vrai! Désolé @jiahao , je blâme le décalage horaire. Le mot que je voulais écrire est «tenseur», et je faisais strictement référence au comportement d'indexation [] . Vous avez raison de dire que la concaténation est une considération importante, et je pense que la manière naturelle de le faire (pour construire une matrice) est raisonnablement évidente (en la considérant comme 1 xn dans ce cas). Clairement, un covecteur n'est pas "dans tous les sens" un 1D Julia Array ... c'est tout le point ...

Les exemples de jiahao me dérangent également. Je serais en faveur de la suppression de tout comportement de singleton de fin possible. La linéarisation des dimensions ultérieures pourrait être utile, mais pourrait aussi encourager une sorte de paresse où vous oubliez le nombre de dimensions de votre tableau ... (je pense que c'est ce qu'implique «ambigu» et «dangereux» ... Julia pour lancer une erreur lorsque je traite un tableau de 16 dimensions comme un tableau de 15 dimensions, par exemple).

lesquels sont ambigus ou dangereux?

Le quatrième me paraît à la fois ambigu et dangereux.

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

Clairement (pour moi) cela devrait être une erreur de syntaxe. A est de rang 3 et la 3ème dimension n'était pas du tout référencée. Il est fort probable que la troisième dimension ait été laissée de côté par erreur et qu'un bogue difficile à trouver a maintenant été introduit dans le code. Je serais reconnaissant d'obtenir une erreur à son point (comme c'est le cas dans IDL et fortran).

Je pense qu'il est préférable que les tableaux ne permettent l'indexation qu'avec le nombre correct d'indices ou le cas particulier de l'indexation linéaire 1D (car c'est extrêmement pratique). Cela inclurait également l'interdiction des singletons de fin.

ou le cas particulier de l'indexation linéaire 1D (car elle est extrêmement pratique)

Comme nos principes se vendent facilement! :)

Je n'ai jamais autant aimé l'indexation linéaire; cela me semble un peu un hack. Souvent, nous voulons simplement le moyen le plus rapide d'itérer sur un tableau. Et pour tous, sauf les tableaux denses simples, l'indexation linéaire peut être très lente. Cela dit, nous ne pourrons peut-être pas éliminer complètement l'indexation linéaire (pour des raisons de performances et pour casser trop de code).

Oui, c'est vrai. Mais au moins l'indexation 1D est visuellement distincte. Alors que l'indexation d'un tableau 6D avec 5D l'est beaucoup moins. Dans tous les cas, je préférerais avoir des règles d'indexation plus strictes et abandonner l'indexation linéaire 1D que d'aller dans l'autre sens. D'autant que l'on peut facilement obtenir une référence remodelée au tableau qui partage la mémoire.

Pouvons-nous utiliser des crochets {} (ou quelque autre) pour l'indexation linéaire et conserver [] pour l'indexation multidimensionnelle? Juste une idée...

Le 23 mars 2015, à 14 h 36, Bob Portmann [email protected] a écrit:

Oui, c'est vrai. Mais au moins l'indexation 1D est visuellement distincte. Alors que l'indexation d'un tableau 6D avec 5D l'est beaucoup moins. Dans tous les cas, je préférerais avoir des règles d'indexation plus strictes et abandonner l'indexation linéaire 1D que d'aller dans l'autre sens. D'autant que l'on peut facilement obtenir une référence remodelée au tableau qui partage la mémoire.

-
Répondez directement à cet e-mail ou consultez-le sur GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -84805310.

Je pense aussi que ce serait bien que nous ayons pu séparer la syntaxe
pour l'indexation linéaire à partir de la syntaxe d'indexation régulière, mais je conviens qu'il
pourrait être un changement radicalement révolutionnaire.

Bien que cette question semble susciter de nombreux débats, la plupart des travaux d'amélioration de l'indexation ont eu lieu dans un assez grand nombre de pull requests tout au long du cycle 0.4. Par exemple, maintenant que nous avons CartesianIndex et des amis, je ne vois pas pourquoi il faudrait séparer la syntaxe de l'indexation linéaire et cartésienne - en effet, vous pouvez maintenant les combiner (# 10524). J'aimerais aussi me débarrasser de l'indexation linéaire, mais parfois elle est plus performante (probablement en grande partie à cause de # 9080; enumerate et zip souffrent du même problème). Nous devrions probablement implémenter fastindex comme wrapper autour de eachindex , comme dans # 10507.

Si vous êtes intéressé par les règles d'indexation, au lieu de marteler ce problème à mort, concentrons-nous sur la frontière la plus intéressante. En ce moment, c'est incontestablement le # 10525. En particulier, https://github.com/JuliaLang/julia/pull/10525#issuecomment -84597488 a besoin d'une sorte de résolution.

@timholy Je n'ai pas vraiment suivi tous les développements sur l'indexation cartésienne rapide, et après avoir jeté un coup d'œil à base / multidimensional.jl, je vois qu'il y a beaucoup de métaprogrammation en cours. Y a-t-il une chance que vous ayez le temps d'écrire (ou de donner une conférence JuliaCon) sur la façon dont tout cela fonctionne?

À certains égards, il n'y a pas grand chose à savoir: bien qu'il se passe pas mal de choses sous le capot, l'idée est de le rendre simple à utiliser. Donc littéralement

k = 0
for I in eachindex(A)
     B[k+=1] = A[I]   # B is being linearly-indexed, A is being cartesian-indexed
end

peut-être tout ce que vous devez savoir. (En d'autres termes, l'aide sur eachindex pourrait être toute la documentation nécessaire.) Cependant, il s'avère que vous pouvez écrire un assez grand nombre d'algorithmes en utilisant quelques extensions de ce paradigme de base; ce que sont ces extensions et pourquoi elles sont puissantes peut, en effet, être moins évident.

J'ai l'intention d'écrire cela dans les mois à venir, mais si les gens veulent vraiment connaître les détails, peut-être qu'une conférence JuliaCon serait raisonnable. @Jutho ou @mbauman pourraient donner ce discours aussi bien que moi.

J'ajouterais plus sur l'ambiguïté, mais je pense qu'une partie de la discussion de fin a résumé certains points clés. Peut-être suis-je plus sensible à ce genre d'ambiguïté en tant qu'étranger ou par peur, au risque de paraître trop dramatique. À mon humble avis, un certain nombre d'exercices de réflexion simples, essentiels à la mission, pourraient vous conduire sur une voie où l'analyse coût / bénéfice d'une simple erreur ne vaut pas la peine.

Nous devrions probablement implémenter fastindex comme wrapper autour de chaque index, comme dans # 10507

@timholy Y a-t-il une raison pour que eachindex retourne un UnitRange pour les tableaux linéaires rapides? Il serait toujours de type stable, et si l'appelant veut un jour s'assurer d'obtenir un CartésianIndex, il peut construire manuellement un CartésianRange (nous pourrions également ajouter une méthode pour eachindex(size(A)) puisque CartesianRange n'est pas exporté ).

C'est ce qu'il a fait au début, mais je pense que @Jutho l'a changé. (Si ce n'est pas vrai, alors je l'ai probablement changé sans m'en rendre compte.) Je suppose que le changement était motivé par souci de cohérence (vous pouvez donc compter sur l'obtention d'un CartesianIndex ), ce qui a un certain sens. . Mais comme vous le faites remarquer, il existe d'autres moyens de garantir cela.

@Jutho , des pensées?

Je ne me souviens pas avoir changé eachindex , mais ça pourrait l'être. Je ne me souviens certainement pas d'une bonne raison, autre que d'avoir un résultat cohérent indépendamment du type de tableau. Mais si la différence entre les tableaux avec et sans indexation linéaire efficace est clairement expliquée dans la documentation, je ne vois aucune raison pour laquelle eachindex ne pourrait pas renvoyer une plage linéaire dans le cas de tableaux avec une indexation linéaire efficace.

@timholy , en réponse à votre message précédent, je ne peux pas assister à JuliaCon alors n'hésitez pas à parler des trucs CartesianIndex (je n'ai de toute façon contribué que quelques touches finales). J'attends avec impatience les rapports et les vidéos de la conférence déjà.

Une pensée fantaisiste: pour un tableau A de n'importe quelle dimension (y compris la dimension> 2), on pourrait avoir A' permuter cycliquement les indices de A . Donc, A'[i, j, k] == A[j, k, i] . Cela se réduirait à la transposition de matrice habituelle pour les tableaux 2d, ainsi qu'à la transposition habituelle pour les «vecteurs» de ligne et de colonne lorsqu'ils sont interprétés comme dans MATLAB (c'est-à-dire comme [n, 1] et [1, n] tableaux 2d respectivement). Ainsi, il ne mapperait jamais un "vecteur" de colonne 2d à un vrai covecteur ou un "vecteur" de ligne 2d à un vecteur vrai. (Je pense que cette propriété est bonne, mais d'autres peuvent ne pas être d'accord.) Cela donnerait les identités A'' == A' et v'' == v _pour les matrices et les vecteurs interprétés comme des objets de tableau_. Étant donné le type de hiérarchie de types discuté ci-dessus (où les vrais vecteurs et les covecteurs sont suffisamment distingués des tableaux abstraits), ' pourrait toujours recevoir une méthode entièrement différente pour les vrais vecteurs et les covecteurs où elle correspond au concept algébrique linéaire et n'a pas besoin de satisfaire v'' == v (mais pourrait si c'est ce que les gens décident de vouloir).

Pour être clair: je ne pense pas une seconde que ' _needs_ avoir une méthode pour les tableaux de dimension> 2, mais je n'avais pas vu cette proposition ci-dessus (à moins que ce soit ce que signifiait "inversion des index ") et j'ai pensé que je ne ferais que le mentionner. Le plus mal que je vois faire (prima facie) est conceptuel: confondre une opération combinatoire (légèrement arbitraire) avec un opérateur normalement pris comme du domaine de l'algèbre linéaire. A cela, on peut répondre qu'au moins un certain degré d'une telle confusion est inévitable lorsque nous essayons d'étendre des concepts algébriques linéaires en concepts purement centrés sur les données (comme le montre toute cette discussion). En tant que tel, nous pouvons aussi bien récolter un raccourci pratique pour les permutations cycliques de tableaux multidimensionnels comme sous-produit de la détermination de ce que ' devrait faire en général pour les dimensions de tableaux multidimensionnels tant que cela se réduit au cas attendu dans 2 dimensions. On pourrait même ajouter une méthode qui prend un argument entier pour que A'(k)[I] permute cycliquement les indices de A[I] k fois, donnant A'(ndims(A))[I] == A[I] et A'(-k)[I] permute les indices dans la direction opposée.

Juste une pensée.

Si transpose est défini pour des tableaux multidimensionnels, je préférerais qu'il satisfasse toujours A''=A , c'est-à-dire que c'est son propre inverse. Ceci est en conflit direct avec la proposition précédente.

C'est juste. Comme je l'ai dit, ma suggestion n'est (potentiellement) attrayante que si l'on est à l'aise de laisser un fossé grandir entre la signification algébrique linéaire de la transposition et la signification centrée sur le tableau que l'on impose dans le cas d> 2. Mon raisonnement était que tant que cette fracture se produisait déjà (en quelque sorte), et si les méthodes seraient simplement totalement inutilisées autrement, il pourrait être intéressant d'avoir un raccourci pour permuter les indices - tant que cela ne nécessite aucun traitement spécial pour faire fonctionner le cas d = 2. Comme vous (@Jutho) l'a mentionné, c'est une coïncidence commode que la transposition de dimensions dans le cas 2d a la signification algébrique linéaire qu'elle a (et seulement après avoir identifié les (co) vecteurs comme des tableaux 2d, d'ailleurs), alors peut-être que nous ne 'pas besoin d'être pointilleux sur les propriétés mathématiques de transpose (par exemple, exiger A'' == A ) pour d> 2. Si l'on souhaite suivre cette voie, il existe de nombreuses méthodes potentiellement utiles qui pourraient être assigné, par exemple: A' permute cycliquement une fois, A'(k) permute cycliquement k fois, A'(I) pour I::Array{Int, 1} avec longueur <= ndims(A) cycliquement permute les indices listés dans I , et A'(p) pour p::Permutation de longueur <= ndims(A) permute les indices selon p . Mais je suppose que la meilleure chose à faire est peut-être de créer un paquet et de voir s'il est suffisamment utile pour comprendre.

Je soutiens deux changements / clarifications qui ont été mentionnés, par exemple, par StefanKarpinski le 16 octobre 2014 et simonbyrne le 22 mars 2015, mais avec une stipulation:

  1. transpose ne doit être utilisé que pour les vecteurs et les matrices à 2 dimensions, pas pour les tenseurs généraux.
  2. Les opérateurs * et transpose doivent supprimer les dimensions du singleton de fin à la fin du calcul. Sinon, les dimensions de singleton de fin peuvent être conservées.

Ces deux changements fourniraient de nombreuses commodités et résoudraient de nombreuses ambiguïtés dans le travail traditionnel d'algèbre linéaire. Ils ne disent intentionnellement rien sur l'indexation générale des tableaux ou l'algèbre tensorielle, qui peuvent être considérées séparément. (En particulier, la transposition de tenseur doit être considérée comme une opération entièrement distincte de la transposition de matrice / vecteur.)

Dans le cadre de cette proposition, lorsque vous travaillez avec la multiplication et la transposition de matrices, il n'y aurait essentiellement aucune distinction entre un vecteur et une matrice à une colonne, ni aucune distinction significative entre un scalaire, un vecteur de longueur 1 et un 1-by -1 matrice. Cependant, x' serait une matrice 1 par n, pas un vecteur.

D'autre part, dans le but de conserver des données, un tableau de n'importe quelle taille pourrait être construit. Aucune dimension singleton ne serait supprimée car les concepts d'algèbre linéaire de multiplication et de transposition ne seraient pas impliqués.

Vous trouverez ci-dessous une transcription hypothétique d'une session Julia avec les modifications proposées.

Tout d'abord, nous définissons un scalaire, deux vecteurs et une matrice.

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 multiplication vectorielle scalaire fonctionne même si des dimensions étrangères sont présentes et le résultat est toujours un vecteur.

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

Transposer est une involution.

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

La multiplication d'une matrice par une matrice à une colonne produit un résultat identique au produit matrice-vecteur.

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

Une matrice à une ligne multipliée par une matrice équivaut à une matrice à une ligne.

julia> x'*A
1x3 Array{Float64,2}:
 30.0  36.0  42.0

Le produit interne est un scalaire et est produit par les règles plus générales pour les produits matrice-vecteur et matrice-matrice.

julia> x'*y
32.0

julia> x'*y[:,[1]]
32.0

Le produit extérieur n'a rien de spécial.

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 vecteur multiplié par un vecteur est une erreur.

julia> x*y
ERROR: `*` has no method matching *(::Array{Float64,1}, ::Array{Float64,1})

Un vecteur multiplié par une matrice est une erreur.

julia> x*A
ERROR: DimensionMismatch("*")
 in gemm_wrapper! at linalg/matmul.jl:270
 in * at linalg/matmul.jl:74

La multiplication matricielle est associative.

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

EDIT: Suppression de deux exemples impliquant la rétrogradation de la dimension intérieure d'un produit. Ils n'avaient pas beaucoup d'incidence sur la discussion actuelle.

Je crains que cela ne perde la stabilité de type des opérations impliquées, car la suppression des dimensions de singleton de fin n'est pas une opération de type stable.

De la façon dont je le vois, toute l'activité de covector consiste à avoir une dimension singleton connue en fonction du type de suppression. De plus, nous avons essayé assez dur de préserver la distinction entre un vecteur et une matrice qui se trouve avoir juste une colonne, mais cette suggestion supprimerait cette distinction dans certains cas, comme x'' .

Suggestion hérétique: que se passerait-il si nous supprimions la dimensionnalité des paramètres de type et effectuions toutes les vérifications de dimensionnalité / taille au moment de l'exécution? (Nous finissons par en faire une bonne partie de toute façon.) Et laissez le paramètre de dimensionnalité pour les types qui codent également la taille en tant que paramètre de type. (Canards, se cache pendant deux semaines et change son compte github en @SomeoneWhoCertainlyIsntTimHolyUhUhNoWay.)

(Bien sûr, je me plaindrais vraiment de moi-même à cause du seul # 10525.)

FWIW, je dirais que ce n'est pas une idée si folle: c'est ainsi que Torch fonctionne, par exemple, et les développeurs de Torch ont exprimé leur mécontentement avec l'encodage de la dimensionnalité de Julia dans le système de types.

@timholy Question: pourrions-nous utiliser cette sémantique améliorée concernant l'algèbre linéaire et les transpositions de vecteurs?

Si l'on était toujours intéressé par l'utilisation de CoVector / DualVector / TransposeVector ceux-ci devraient encore envelopper notre nouveau TimHolyArray{DataType} _et_ nous devrons soit donner un sens à la transposition d'un tableau de dimension supérieure à deux (ou une), ou bien interdire la construction TransposeVector(tharray) quand tharray a une dimension supérieure à deux (ou une) ... En fait toutes sortes de les choses devront donner des erreurs au niveau de l'exécution qui sont actuellement des erreurs de compilation (comme les multiplications qui sont actuellement indéfinies et donc interdites par le système de types).

D'un autre côté, l'implémentation d'un drapeau de transposition dans cette nouvelle classe peut être mauvaise ... cela ajoute plus de complexité à ce qui devrait être un conteneur efficace et léger. J'aurais tendance à exclure cette option et à laisser le travail acharné au système de compilation / type.

Je ne suis pas nécessairement contre votre idée - mais cela semble être un problème supplémentaire qui pourrait être fait en parallèle avec les transpositions vectorielles.

@timholy : Je suis vraiment sûr que c'est la voie à suivre ou non. Il y a des situations où je trouve très utile de dépêcher sur la dimension.

La suggestion était de faire en sorte que cela s'applique à tous les tableaux. Mais je suis contre ma proposition maintenant, simplement parce que pour de nombreux cas multidimensionnels dont vous avez besoin pour générer N boucles pour N tableaux de dimensions. Nous ne pourrions plus faire cela si N n'était pas un paramètre de type.

Oui, n'est-ce pas tout l'intérêt de vos macros cartésiennes (ou de la fonction mise en scène à laquelle je ne suis toujours pas habitué :-))?
J'ai fait des choses similaires en C ++ où c'est vraiment pénible d'avoir la dimension comme paramètre de modèle. Mais j'ai eu des situations où la variante dynamique était limitative car il fallait de grandes instructions if pour spécialiser les différentes dimensions du tableau.

Proposition:

  1. Renommez le comportement de multiplication de la matrice actuel en timesfast et le comportement de transposition actuel en transposefast .
  2. Modifiez (*) et transpose pour tronquer les dimensions de singleton de fin comme dans le commentaire précédent . Par exemple, u'*v deviendrait un scalaire, v'' deviendrait un vecteur et (v*v')/(v'*v) serait défini.

Le comportement existant est de type stable. Le comportement proposé suit les conventions de nombreux textes d'algèbre linéaire. Ils sont tous deux précieux. Peut-être que Julia devrait avoir les deux.

Je veux utiliser Julia dans la salle de classe, donc je vote pour le comportement par défaut qui valorise la commodité plutôt que l'efficacité.

Merci à plusieurs contributeurs de m'avoir mis au courant sur ce très long fil!

@briansutton : Je pense que vous devriez vraiment reconsidérer ce que vous demandez. Je vous encourage à attendre d'avoir une meilleure compréhension du fonctionnement de Julia avant de proposer que nous redéfinissions le sens de la multiplication.

Ce problème a été de savoir comment éviter autant de cas particuliers que possible.

Les règles qui permettent de truquer les dimensions singleton se décomposent au moment où l'une des dimensions singleton est celle qui vous tient vraiment à cœur. Avec la règle «truncate [all] trailing singleton dimensions», alors si v est un vecteur à 1 élément, alors v' est un scalaire, et donc v'' est aussi un scalaire. Donc, même dans cette proposition, la propriété que v == v'' ne tient pas toujours.

Vous pouvez essayer de modifier la règle pour "tronquer uniquement la dernière dimension de singleton de fin si le tableau a une dimensionnalité 2". Mais même avec cette règle modifiée, le produit externe v * w' ne découle pas automatiquement de la définition de la multiplication matricielle, mais doit plutôt être sa propre définition à casse spéciale de Vector * Matrix , et la définition doit be "jette une erreur sauf si les formes sont (N) x (1, M)".

@jiahao :

Lorsque je fais de l'algèbre linéaire avec des vecteurs et des matrices, je ne veux pas faire de distinction entre un scalaire, un vecteur à 1 élément et une matrice 1 par 1. Actuellement, diverses opérations produisent l'un des trois objets. Ma suggestion est, pour les opérations de multiplication matricielle et de transposition, de choisir l'une des trois comme représentation préférée.

Concernant votre premier exemple ( v==v'' ), j'aimerais éviter de construire un vecteur à 1 élément v en premier lieu.

En ce qui concerne votre deuxième exemple, oui, je pense que v*w' devrait être traité comme vous le décrivez. Lorsque je travaille avec la multiplication et la transposition de matrices, je veux que le vecteur v et la matrice N-par-1 v[:,[1]] désignent le même objet mathématique, même s'ils ont des représentations internes différentes. Cela nécessiterait un code spécial pour gérer la matrice de N-vecteurs fois 1 par M.

Je suppose que vous n'êtes toujours pas convaincu que la stabilité du type est importante.

@briansutton : Je pense que vous trouveriez très instructif d'essayer d'implémenter les machines nécessaires pour que votre proposition s'exécute aussi vite que le système de type actuel permet au code Julia de s'exécuter. Pour ma part, je pense que vos objectifs déclarés sont irréalisables étant donné les objectifs juliens de fournir des performances prévisibles et la compatibilité de la disposition de la mémoire C.

Je ne sais pas si je comprends les arguments qui vont et viennent, mais une chose a attiré mon attention. C'est peut-être un vœu pieux, mais ce serait bien si l'ordinateur pouvait penser aussi vite que le cerveau humain. Ce que je veux dire, c'est que lorsque Brian Sutton parle du programme "choisir une représentation préférée", j'envisage un programme qui peut penser, tout comme nous le pouvons, lorsque nous faisons des maths. Peut-être que ce n'est pas faisable avec la technologie actuelle et ralentira trop les choses. Mais ça ne serait pas sympa ...

Je suis physicien et utilisateur actif de Julia.

Je n'ai plus une forte préférence pour le maintien des conditions météorologiques ou l'abandon de toutes les dimensions du singleton final

Mais ici, je voudrais soulever une question étroitement liée.

L'implémentation actuelle de Julia:

Soit V un tenseur de rang 1 à 3 dimensions (un vecteur)
V [1] nous donnera un scaler pas un tenseur de rang 1 1-dim

Soit A un tenseur 3x4x5 de rang 3
B = A [1,:,:] nous donnera un tenseur 1x4x5 rang 3.

Les deux comportements ci-dessus ne sont pas tout à fait cohérents.

Je préfère fortement la fonction d'indexation / glissement suivante:

Soit A un tenseur 3x4x5 de rang 3
B = A [1,:,:] nous donnera un tenseur 4x5 rang-2.
C = A [1: 1,:,:] nous donnera un tenseur 1x4x5 rang-2.
(actuellement les deux ci-dessus donnent le même résultat)

Soit V un tenseur de rang 1
B = V [1] nous donnera un scaler
C = V [1: 1] nous donnera un tenseur de rang 1 à 1 dim.

Cette fonctionnalité nous aidera à changer la forme du tenseur plus facilement, et sera utile lorsque nous autoriserons les indices de singleton à la fin.

Meilleur

Xiao-Gang


De: esd100 [[email protected]]
Envoyé: mardi 9 juin 2015 21:46
À: JuliaLang / julia
Cc: Xiao-Gang Wen
Sujet: Re: [julia] Prendre au sérieux les transpositions vectorielles (# 4774)

Je ne sais pas si je comprends les arguments qui vont et viennent, mais une chose a attiré mon attention. C'est peut-être un vœu pieux, mais ce serait bien si l'ordinateur pouvait penser aussi vite que le cerveau humain. Ce que je veux dire, c'est que lorsque Brian Sutton parle du programme "choisir une représentation préférée", j'envisage un programme qui peut penser, tout comme nous le pouvons, lorsque nous faisons des maths. Peut-être que ce n'est pas faisable avec la technologie actuelle et ralentira trop les choses. Mais ça ne serait pas sympa ...

-
Répondez directement à cet e-mail ou consultez-le sur Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -110554622.

Je veux dire: C = A [1: 1,:,:] nous donnera un tenseur 1x4x5 rang 3.

Xiao-Gang


De: Xiao-Gang Wen [[email protected]]
Envoyé: lundi 22 juin 2015 à 12 h 01
À: JuliaLang / julia; JuliaLang / Julia
Cc: Xiao-Gang Wen
Sujet: RE: [julia] Prendre les transpositions vectorielles au sérieux (# 4774)

Je suis physicien et utilisateur actif de Julia.

Je n'ai plus une forte préférence pour le maintien des conditions météorologiques ou l'abandon de toutes les dimensions du singleton final

Mais ici, je voudrais soulever une question étroitement liée.

L'implémentation actuelle de Julia:

Soit V un tenseur de rang 1 à 3 dimensions (un vecteur)
V [1] nous donnera un scaler pas un tenseur de rang 1 1-dim

Soit A un tenseur 3x4x5 de rang 3
B = A [1,:,:] nous donnera un tenseur 1x4x5 rang 3.

Les deux comportements ci-dessus ne sont pas tout à fait cohérents.

Je préfère fortement la fonction d'indexation / glissement suivante:

Soit A un tenseur 3x4x5 de rang 3
B = A [1,:,:] nous donnera un tenseur 4x5 rang-2.
C = A [1: 1,:,:] nous donnera un tenseur 1x4x5 rang-2.
(actuellement les deux ci-dessus donnent le même résultat)

Soit V un tenseur de rang 1
B = V [1] nous donnera un scaler
C = V [1: 1] nous donnera un tenseur de rang 1 à 1 dim.

Cette fonctionnalité nous aidera à changer la forme du tenseur plus facilement, et sera utile lorsque nous autoriserons les indices de singleton à la fin.

Meilleur

Xiao-Gang


De: esd100 [[email protected]]
Envoyé: mardi 9 juin 2015 21:46
À: JuliaLang / julia
Cc: Xiao-Gang Wen
Sujet: Re: [julia] Prendre au sérieux les transpositions vectorielles (# 4774)

Je ne sais pas si je comprends les arguments qui vont et viennent, mais une chose a attiré mon attention. C'est peut-être un vœu pieux, mais ce serait bien si l'ordinateur pouvait penser aussi vite que le cerveau humain. Ce que je veux dire, c'est que lorsque Brian Sutton parle du programme "choisir une représentation préférée", j'envisage un programme qui peut penser, tout comme nous le pouvons, lorsque nous faisons des maths. Peut-être que ce n'est pas faisable avec la technologie actuelle et ralentira trop les choses. Mais ça ne serait pas sympa ...

-
Répondez directement à cet e-mail ou consultez-le sur Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -110554622.

Ce que vous demandez, c'est comment fonctionne actuellement slice . Mon sentiment est que A[stuff] deviendra un synonyme de slice(A, stuff) , et donc vous obtiendrez probablement votre souhait.

Cher Tim:

Merci pour le tuyau. J'ai essayé slice. Cela ne correspond pas à mes besoins. Slice produit un nouveau type de données "sous-tableau" que je ne peux pas utiliser dans mon autre code qui utilise le type de données :: Array.

Peut-être que je peux changer mon autre code pour qu'il autorise le type de données "sous-tableau".

Xiao-Gang


De: Tim Holy [[email protected]]
Envoyé: lundi 22 juin 2015 17:32
À: JuliaLang / julia
Cc: Xiao-Gang Wen
Sujet: Re: [julia] Prendre au sérieux les transpositions vectorielles (# 4774)

Ce que vous demandez, c'est comment slice fonctionne actuellement. Mon sentiment est que A [trucs] deviendra synonyme de slice (A, trucs), et donc vous aurez probablement votre souhait.

-
Répondez directement à cet e-mail ou consultez-le sur Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -114268796.

Y a-t-il quelque chose qui dépend vraiment de l'utilisation des types concrets Array au lieu de AbstractArray dans votre code? Cela peut ne nécessiter rien de plus qu'une recherche / remplacement de Array par AbstractArray pour que les choses fonctionnent.

Cher Scott

Merci beaucoup pour le tuyau.

Xiao-Gang


De: Scott P. Jones [[email protected]]
Envoyé: jeudi 25 juin 2015 09:55 AM
À: JuliaLang / julia
Cc: Xiao-Gang Wen
Sujet: Re: [julia] Prendre au sérieux les transpositions vectorielles (# 4774)

Y a-t-il quelque chose qui dépend vraiment de l'utilisation des types Array concrets au lieu de AbstractArray dans votre code? Cela peut ne nécessiter rien de plus qu'une recherche / remplacement de Array par AbstractArray pour que les choses fonctionnent.

-
Répondez directement à cet e-mail ou consultez-le sur Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -115265047.

Cela a-t-il fait l'affaire pour vous? Je suis heureux de vous aider!

@wenxgwen , vous pouvez également appeler copy(slice(A,...)) afin d'obtenir une copie de la tranche dans un nouveau Array qui devrait alors fonctionner nativement avec vos fonctions déjà existantes.

Résoudre ce problème est maintenant devenu une entreprise lucrative

Depuis la résolution de ce problème est maintenant critique sur mon chemin vers 3 virgules ...
image

Mais sérieusement. J'ai essayé de lire complètement la discussion mais je ne peux pas garantir que cela n'ait pas été suggéré auparavant:

Pouvons-nous éventuellement étendre la définition AbstractArray pour avoir un trait supplémentaire (similaire à LinearIndexing?) Qui définit si les données sous-jacentes sont un stockage basé sur des lignes ou sur des colonnes? Cet ajout ouvrirait les propriétés suivantes (veuillez ne pas vous concentrer sur les noms ... juste des concepts):

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)

De toute évidence, cette proposition manque beaucoup de définitions, mais peut-être qu'elle peut lancer la discussion. Pouvons-nous prendre en charge le stockage d'index de colonne et d'index de ligne en même temps, et «résoudre» certains problèmes en cours de route?

J'aimerais beaucoup que Julia soit un stockage basé sur les lignes, car c'est naturellement ainsi que je pense aux boucles et à de nombreuses autres opérations. (et je ne pense pas que je sois le seul à penser de cette façon) Commentaires s'il vous plaît!

C'est une pensée intéressante, demander au constructeur de prendre également en ligne ou en colonne. Je pense que cela a déjà été discuté quelque part dans le système de problèmes GitHub. Je peux me tromper!

Personnellement, je préfère le stockage basé sur des colonnes parce que la plupart de mes manuels utilisent des colonnes pour leurs mathématiques, puis dans Julia, je n'ai pas besoin de tout changer pour utiliser des lignes à la place. J'ai également trouvé cela étrange au début, mais c'est rapidement devenu un non-problème pour mon travail. Cependant, il existe des algorithmes qui sont plus faciles à exprimer en lignes, c'est pourquoi cela pourrait être intéressant de pouvoir utiliser un stockage non standard lorsque cela est nécessaire. J'espère que cette convention serait de toujours renvoyer une matrice principale de colonne ou un vecteur de colonne lorsque vous avez une fonction qui est exportée de telle sorte qu'il n'y ait jamais de question sur le type que vous avez lors de l'appel d'une fonction. Sinon, cela pourrait devenir assez désordonné et devenir désagréable à utiliser lorsque vous devez toujours rechercher le type renvoyé.

Basé sur des colonnes ou basé sur des lignes ne fait pas partie de ce problème.

@tbreloff J'aime beaucoup l'idée. Ce serait formidable de pouvoir s'interfacer plus facilement avec les langages / bibliothèques qui sont en ligne.

Jiahao a raison de dire que préférer la ligne principale à la colonne principale est hors sujet. Ses
juste un bel effet secondaire qui résout le problème de transposition de manière "julienne"
(types paramétriques et fonctions par étapes) donne aux gens plus de flexibilité
format de stockage.

Si vous n'aimez pas l'idée d'ajouter un trait ligne / colonne aux tableaux, alors le
exactement la même chose peut être accomplie avec un TransposeView {T, N}, mais je
soupçonne que ce sera plus compliqué à bien mettre en œuvre.

Le plus gros inconvénient du trait supplémentaire est la confusion pour les nouveaux utilisateurs,
et c'est quelque chose que j'ai du mal à concilier.

Le samedi 26 septembre 2015, Scott P. Jones [email protected]
a écrit:

@tbreloff https://github.com/tbreloff J'aime beaucoup l'idée. Il
serait formidable de pouvoir s'interfacer plus facilement avec les langues / bibliothèques
qui sont des lignes majeures.

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -143436947.

La surcharge d'appels modifie-t-elle du tout l'espace de conception? Parfois, dans ce qui précède, il semblait y avoir une sorte d'ambiguïté dans la signification de * . En particulier, lorsque nous voulons que v::Covector * w::Vector renvoie un scalaire, considérons-nous que * réellement "map w sous v " au lieu d'une multiplication matricielle? Si tel est le cas, ne pourrait-on pas exiger de la même manière que w::Vector * v::Covector renvoie un scalaire, puisque les vecteurs sont eux-mêmes des cartes linéaires sur des covecteurs?

Il serait peut-être utile de surcharger call et d'écrire v(w) pour désigner l'opération "map under v " sur w , et de même pour w(v) - les deux renverraient des scalaires. Cela permettrait-il plus de marge de manœuvre pour accommoder la sémantique que les opérations de tableau et les opérations algébriques linéaires partagent dans le cas des tableaux 2D?

Si tel est le cas, ne pourrait-on pas exiger de la même manière que w :: Vector * v :: Covector renvoie un scalaire, puisque les vecteurs sont eux-mêmes des cartes linéaires sur des covecteurs?

Je pense que nous essayons de suivre les conventions de matrice et de vecteur que beaucoup de gens voient en première année à l'université, qui est non commutative et a vecteur * transposé vecteur -> matrice (de rang 1, c'est-à-dire n'ayant qu'un seul singulier (non nul) valeur). Ce que vous avez écrit ressemble plus à un concept abstrait d'algèbre linéaire, où le vecteur et les co / vecteurs doubles sont des cartes les uns des autres vers un scalaire, ce qui est bien mais un peu différent de ce qui (à mon humble avis) est tenté dans Julia et MATLAB. Je pense que nous pourrions interpréter ce que vous avez écrit comme le produit interne, dot() ou agissant sur deux covecteurs (que nous devrions probablement définir pour les covecteurs, je pense), mais parfois nous voulons aussi l'extérieur -product, et actuellement le non-cumulatif * nous permet d'écrire et d'exprimer les deux comportements.

En ce qui concerne la convention d'appel v(w) , nous pourrions également dire pour le produit scalaire que nous voulons le montant de v dans le sens de w ce qui suggère d'utiliser l'opérateur d'indexation v[w] . (BTW, je ne dis pas que c'est une bonne option de conception de langage - juste une observation!)

nous pourrions également dire pour le produit scalaire que nous voulons le montant de v dans le sens de w ce qui suggère d'utiliser l'opérateur d'indexation v[w] .

Cette suggestion est presque conforme, mais non, à la sémantique de l'indexation. Cela se heurte également à notre support actuel pour l'indexation vectorielle si v est un Vector{<:Integer} .

Considérez que pour v :: Vector{T<:Real} , v[1] est équivalent au produit scalaire v⋅e₁ , où e₁ est le vecteur de base canonique le long du premier axe. Par conséquent, l'indexation simple est vraiment la fonction

   v[n] : n :: Integer --> y = (v ⋅ eₙ) :: T

Pour l'indexation vectorielle, v[[1, 2]] produit [v⋅e₁, v⋅e₂] qui est le résultat de la projection de v dans le sous-espace couvert par {e₁, e₂} , ou de manière équivalente c'est le résultat de v' * [e₁ e₂] .

L'indexation vectorielle est donc la fonction

   v[I] : I :: Vector{<:Integer} --> y = v' * [eₙ for n in I] :: Vector{T}

La proposition de rendre v[w] = (w ⋅ v) v est incompatible avec cette définition car elle élimine le mappage implicite d'une collection d'indices n (comme spécifié par w ) à une collection de vecteur de base canonique eₙ , ce qui est nécessaire pour que nos règles d'indexation actuelles fonctionnent.

En limitant la portée à la seule transposition vectorielle pour l'instant, je pense que nous avons deux options. Nous pouvons soit en faire une erreur, soit introduire le type de covecteur spécial. Étant donné la nature de première classe du vecteur dans Julia, je pense que le premier serait une vente très difficile… et pour le faire de manière cohérente, nous devrions probablement interdire complètement aux vecteurs de participer entièrement à l'algèbre des matrices.

Alors j'ai tiré sur le covecteur. C'est beaucoup de travail et je ne pourrai malheureusement pas y consacrer plus de temps. Je la poste ici dans l'espoir que quelqu'un courra avec ou que nous apprendrons des difficultés et déciderons de nous enfuir. Mais j'ai un bâtiment de branche avec des transpositions comme vues et une multiplication matricielle implémentée avec des covecteurs. Certains tests sélectionnés réussissent, mais seulement sans depwarn=error (par exemple, ./julia -e 'using Base.Test; include("test/matmul.jl")' ). https://github.com/JuliaLang/julia/compare/mb/transpose

J'ai défini deux nouveaux types de vues à utiliser pour transpose et ctranspose de matrices et de vecteurs:

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

Le paramètre C est un simple booléen pour représenter si la transposition est une transposition ou non. Notez que le Covector n'est _pas_ un sous-type de AbstractArray ; c'est une exigence assez forte pour que la répartition fonctionne de manière raisonnable.

Quelques inconvénients:

  • Les Covectors sont décidément compliqués, et ils seront un défi à documenter de manière accessible et rigoureuse. Le langage pourrait aider ici - nous pourrions simplement les appeler RowVector s. Quoi qu'il en soit, la toute première difficulté que vous rencontrez en essayant d'expliquer ce comportement est de savoir comment vous parlez de la forme d'un covecteur ( size n'est pas défini). Si (m,n) est la forme d'une matrice, alors (m,) peut représenter la forme d'un vecteur ... et si nous abusons de la notation tuple, nous pouvons décrire de manière argotique un covecteur pour avoir la forme (,n) . Cela nous permet d'exprimer des règles mixtes d'algèbre vectorielle / matricielle avec une dimension "manquante" qui se combine et se propage de manière assez sensible:

    • Matrix * Vector est (m,n) × (n,) → (m,)

    • Covector * Matrix est (,m) × (m,n) → (,n)

    • Vector * Covector est (n,) × (,n) → (n,n)

    • Covector * Vector est (,n) × (n,) → α (scalaire)

  • Le nombre d'opérations et de types binaires conduit ici à une énorme explosion combinatoire du nombre de méthodes à définir ... juste pour la multiplication, nous avons:

    • Mutation: (mutante, non mutante)
    • Transposer: (A, Aᵀ, Aᴴ) × (B, Bᵀ, Bᴴ).
    • Forme: (Mat × Mat; Vec × Mat; Mat × Vec, Vec × Vec). Notez que tous ces éléments ne sont pas pris en charge dans toutes les combinaisons de transpositions, mais la plupart le sont.
    • Implémentation: (BLAS, Strided, générique, plus spécialisations pour les matrices structurelles)

    Bien que certaines de ces opérations s'alignent bien avec plusieurs comportements de répartition et de repli, il s'agit toujours d'un nombre énorme de méthodes pour chaque opérateur. Supprimer complètement le support de matrice / vecteur mixte ici aiderait certainement à simplifier les choses.


  • Ils ne résolvent immédiatement aucune des difficultés liées à la suppression de toutes les dimensions scalaires. La prise en charge de toute sorte de transposition vectorielle lorsque nous supprimons des dimensions scalaires nous met dans un territoire ambigu en ce qui concerne le conjugué complexe (voir https://github.com/JuliaLang/julia/pull/13612). Peut-être que nous pourrions avoir A[1,:] renvoyer un covecteur, mais cela ne se généralise pas bien et serait assez étrange de renvoyer un non-AbstractArray à partir d'une tranche. Ce serait formidable si les gens pouvaient essayer # 13612 et rechercher spécifiquement les cas où la transposition conjuguée d'un vecteur cause des problèmes - ce serait tout aussi mauvais avec la sémantique de transposition vectorielle actuelle et ne nécessite pas que Covectors soit exposé.

Quelques avantages:

  • L'utilisation de dispatch directement au lieu de l'analyse spéciale de Ax_mul_Bx est une énorme victoire. Il se compose très bien avec l'API de BLAS. En général, vous voulez faire la conjugaison aux niveaux les plus profonds des algorithmes, il est donc beaucoup plus logique de conserver ces informations avec les arguments. Pour les appels BLAS, une fonction simple peut être utilisée pour rechercher le caractère de transposition ( ntc ).
  • La multiplication entre vecteurs et covecteurs est désormais associative car v'v renvoie un scalaire. Vous pouvez maintenant évaluer v'v*v de gauche à droite et éviter de former la matrice.
  • Cela permet de supprimer Vector * Matrix, qui ne fonctionne que si vous traitez tous les vecteurs comme s'ils avaient des dimensions de singleton de fin… et c'est un grand pas vers la suppression complète du support des dimensions de singleton de fin en général.

Autres notes:

  • Avoir la complexité de la transposition représentée par un paramètre de type booléen fonctionne bien, mais j'ai souvent eu l'impression d'avoir défini les paramètres dans le mauvais ordre. J'ai rarement voulu restreindre ou capturer à la fois T et C . Je ne suis pas sûr si le faire dans l'autre sens serait mieux en termes de nombre de types d'espace réservé que vous devez définir, mais à tout le moins, cela correspondrait à AbstractArray{T} .
  • Cela exacerbe vraiment les difficultés de StridedArray. La lecture de la table des méthodes pour * est tout un défi avec des types comme ::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}} . Bien sûr, je l'ai écrit sous forme de typealias, mais même gérer tous ces noms d'alias de types différents est une douleur ( StridedMatOrTrans , QRCompactWYQorTranspose , etc.).
  • Je n'ai pas eu la chance d'intégrer cela avec le SparseVector, mais ce sera une énorme victoire car il représentera la transposition efficacement sans avoir besoin d'un CSR.
  • Faut-il définir une indexation scalaire et / ou non scalaire sur les Covectors? Si oui, quel est le résultat de r[:] ou r[1:end] ? Est-ce un vecteur ou un covecteur? Je pense que j'essaierais de m'en sortir sans définir cela si nous le pouvons. Fait intéressant, Matlab a des règles très spéciales pour indexer les vecteurs de lignes avec d'autres vecteurs - ils essaient très fort de maintenir leur rang au prix de quelques cas d'angle étranges ( r((1:end)') is r(1:end) is r(:)' ). L'indexation scalaire est définie dans ma branche pour l'instant, mais peut-être que cela devrait également être supprimé. Cela indiquerait clairement que le Covector ne doit être utilisé qu'avec des opérations d'algèbre linéaire qui le connaissent.

Enfin, je veux juste souligner que la plupart des avantages que je vois ici sont tout aussi applicables au type MatrixTranspose en lui-même. Je pense que cela démontre que le Covector peut très bien fonctionner sans aller trop loin dans les algèbres abstraites pour permettre des opérations mixtes Vector / Matrix. Cela ajoute définitivement une fonctionnalité intéressante (la possibilité d'utiliser des vecteurs de manière cohérente avec l'algèbre linéaire), mais je ne suis pas sûr que cela vaille la complexité supplémentaire.

Par curiosité, si vous vous souvenez encore de @mbauman , qu'est-ce qui vous a motivé à introduire le paramètre booléen C ? Ne peux-tu pas juste avoir (dans les pseudotraits)

transpose(::Matrix) -> MatrixTranspose
transpose(::MatrixTranspose) -> Matrix

? Je ne doute pas de votre jugement (exceptionnel) ici, je veux juste comprendre quelles réalisations vous ont forcé à faire cela.

On pourrait probablement généraliser cela à un type PermutedDimensionArray , avec un paramètre tuple codant la permutation. Le type Covector a évidemment un but différent et doit être une chose distincte.

Vous avez besoin d'un moyen de gérer les transpositions conjuguées (et aussi les tableaux conjugués non transposés pour (A').' ). Je vois trois façons évidentes de le faire:

  • Stockez la conjugaison sous forme de champ booléen dans un seul type MatrixTranspose . Après réflexion, je pense que c'est certainement la meilleure option pour l'interfaçage avec un BLAS externe. En termes de JuliaBLAS native, cependant, je voudrais m'assurer que Julia / LLVM est capable de sortir T.isconjugate ? conj(T[i,j]) : T[i,j] des boucles.
  • Un type avec un paramètre de conjugaison. C'est ce que j'ai choisi, et je pense avoir été influencé par le fait que nous faisons actuellement de la pseudo-dispatch sur le conjugué-ness par le biais de Ac_mul_Bt et amis. Il offre également des garanties plus solides sur les optimisations de suppression de branche que Julia est capable de faire. Mais je n'y ai pas beaucoup réfléchi… Je voulais juste démarrer une implémentation de croquis, et j'étais plus préoccupé par le Covector.
  • Deux types distincts, MatrixTranspose et MatrixCTranspose . Isomorphe à un paramètre de type, mais je trouve deux types de wrappers distincts ennuyeux. Les supertypes abstraits et les alias d'union peuvent aider, mais je choisirais toujours le paramètre plutôt que cette option.

Je suppose qu'il y a maintenant une quatrième option avec des fonctions typées, stockant n'importe quelle fonction de transformation dans le type transpose ... mais cela nécessite également un paramètre de type pour que la fonction soit rapide, et c'est alors un paramètre de type qui n'est pas facilement distribué non plus.

Je me demande si nous pourrions avoir un wrapper ConjugateView , avec la règle conj et transpose garantissant que ConjugateView placé dans le wrapper MatrixTranspose . Ie, pour A a Matrix ,
A' = conj(transpose(A)) = transpose(conj(A)) tous produisent un MatrixTranspose{ConjugateView{Matrix}} (en supprimant les paramètres de type non informatifs).

Ah, oui, c'est raisonnable aussi. J'ai tendance à penser à la transposition conjuguée comme une «chose» atomique, donc j'ai raté cette option. Je pensais à la façon dont les transpositions non conjuguées pourraient être représentées par un type d'index de sous-tableau spécial, tout comme les remodelages.

Je suis content que vous y travailliez encore! C'est un fil épique! Trois hourras!!!

Est-ce prévu pour la version 1.0?

Dans deux numéros (# 18056, # 18136), j'ai été pointé vers ce fil géant.
Alors je fais un essai.

Maintenant Julia a de vrais vecteurs à 1 dimension, _ par exemple_ mx[row,:] n'est plus une matrice 1xn.
C'est un changement bienvenu!

Mais comme effet secondaire, certains problèmes existants sont devenus plus évidents.
J'ai été mordu par le fait que v*mx ne fonctionne pas pour les vecteurs à 1 dimension.
Mathématiquement, cela devrait fonctionner naturellement et renvoyer un vecteur 1-d,
lorsque le produit général a*b est défini par contrat
le dernier indice du premier terme et le premier indice du second terme.

Actuellement, la signature de méthode du produit Vector-Matrix est:
(*)(A::AbstractVector, B::AbstractMatrix) = reshape(A,length(A),1)*B
et cette méthode est utilisée pour le cas v*v' et non pour v*mx .
(Merci @andreasnoack de l' avoir signalé.)
De toute évidence, une seule méthode ne peut pas être utilisée pour les deux.

Il semble que Julia est aux prises avec des coventions à la Matlab.
Mais dans Matlab il n'y a pas de vecteurs 1-d, juste des matrices 1xn et nx1,
tant de choses naturelles peuvent poser problème ici.
Julia a de vrais vecteurs 1-d, ce qui devrait être un gros avantage.
Ce serait bien d'atteindre un état vraiment cohérent.

Fortran est un exemple bien meilleur et plus cohérent à suivre dans ce cas.
L'opération transpose est définie uniquement pour les matrices en Fortran,
créer une matrice 1xn à partir d'un vrai vecteur 1-d n'est tout simplement pas une transposition.
Pour matmul voir l'extrait du livre Metcalf cité dans # 18056.

Je pense que la plupart des points originaux de @alanedelman étaient corrects.

Voici donc une suggestion qui permettrait simplement de résoudre certains problèmes existants,
tout en respectant au maximum l'état actuel:

  • garder v' tel quel pour créer une matrice 1xn à partir d'un vrai vecteur 1-d v
  • rowmx et colmx seraient meilleures, mais v' est trop répandu pour le changer
  • nous avons déjà la fonction vec pour créer un vrai vecteur 1-d
  • bien que l'inverse de v' ne soit pas v'' mais vec(v') , nous pouvons vivre avec
  • le produit a*b doit toujours contracter le dernier index de a et le premier index de b
  • l'opérateur * ne doit pas être utilisé pour les produits internes ou externes
  • pour les produits internes, nous avons déjà la fonction dot
  • pour les produits externes, l'utilisation de * devrait être éliminée (_i.e._ la syntaxe v*v' )
  • pour les produits extérieurs, une nouvelle fonction doit être utilisée
  • un opérateur d'infixe correspondant pourrait garder la syntaxe légère

Perdre [une syntaxe mathématique concise pour] des produits externes serait malheureux. Je préfère abandonner personnellement vec * mat.

Le PernutedDimsArray existant peut-il déjà gérer le cas où le parent et la vue ont des nombres de dimensions différents? Si tel est le cas, c'est peut-être déjà utilisable comme type de wrapper de transposition non conjugué, même pour les parents de vecteurs.

Je n'ai pas trouvé PermutedDimsArray dans la documentation.

Mais je pense qu'au lieu d'inventer encore plus de types de données,
seuls les trois types de produits de réseau doivent être clairement séparés.
Les produits intérieurs sont déjà séparés.
Nous devons seulement séparer les produits normaux et externes.
Les produits extérieurs ne seraient pas perdus, seule leur syntaxe changerait.
Veuillez considérer le cas v*mx uniquement comme un symptôme du problème plus profond.

Mis à part le "problème des méthodes manquantes", ce ne serait pas un type dont vous auriez à vous soucier, ce serait un détail d'implémentation que .' renvoie un type de wrapper paresseux (et ' une version conjuguée de celui-ci). Sinon, je ne pense pas que nous pourrions avoir à la fois vec*mat et vec*vec' utilisant le même opérateur * . Si je voyais vec*mat dans un article, cela me semblerait faux, mais je vois vec*vec' assez fréquemment.

Cependant, en y réfléchissant davantage, je pense que PermutedDimsArray ne transpose pas de manière récursive ses éléments pour des tableaux de tableaux, donc ce n'est pas tout à fait approprié comme type de wrapper à utiliser ici.

L'autre alternative consiste à interdire complètement la transposition vectorielle. Je pense que la discussion ici a déjà été trop approfondie, et nous attendons simplement une mise en œuvre complète de l'une ou des deux options pour évaluer à quoi ressemblera l'impact.

J'apprécie que vous étiez prêt pour une discussion alors que ce fil touche à sa fin.

Bien que v*mx puisse vous paraître étrange, il est fortement utilisé dans le code cristallographique.
Il est également bien géré par le matmul de Fortran. (Voir # 18056)

Retour au produit u*v' .
Quand u et v sont tous les deux des matrices nx1, c'est un produit matriciel normal,
cela s'avère donner le même résultat que le produit extérieur.
Mais il s'agit simplement d'utiliser le produit matriciel pour émuler le produit extérieur.
Ceci est transparent dans le monde de Matlab où tout est une matrice.

Chez Julia, nous avons de vrais vecteurs 1-d plus proches du monde de Fortran.
Dans Julia le vecteur transposé v' est déjà un problème,
Le choix de Fortran est de l'interdire, comme cela a déjà été suggéré à Julia par d'autres.

Fortran n'a aucune fonction intrinsèque pour les produits extérieurs.
Julia transforme facilement v' en une matrice 1xn,
et exécute l'opération * sur un vecteur 1-d et une matrice 1xn.
En raison de l'envoi multiple, il est capable de le faire,
mais ici * n'est sûrement plus un produit matriciel.

Le point où Fortran et Julia se comportent de la même manière
est qu'ils utilisent tous les deux une fonction dot distincte pour les produits internes.
Il y a eu une discussion pour regrouper dot également dans l'opérateur * ,
mais heureusement cela ne s'est pas produit.

Nous devons donc gérer les trois produits de l'algèbre linéaire:
produit normal de matrice, produit intérieur et produit extérieur.
Actuellement, l'opérateur * est une syntaxe combinée pour les produits normaux et externes.
Tout ce que j'ai suggéré, c'est de les séparer et d'atteindre un état plus cohérent.

(Oh, j'ai laissé de côté le produit croisé, mais il est déjà bien séparé)

Je ne pense pas que le produit intérieur, les produits extérieurs et la multiplication matricielle soient un moyen mathématiquement valable de diviser / classer les produits. Ce qui suit est certainement une répétition de choses qui ont été énoncées ci-dessus par diverses personnes, mais étant donné la longueur de ce sujet, j'espère que vous pouvez inclure un résumé de temps en temps. Ceci est mon résumé personnel et je ne suis certainement pas un expert alors corrigez-moi là où je me trompe.

Dans un cadre d'algèbre linéaire abstraite, les principaux acteurs sont les vecteurs v (vivant dans un espace vectoriel V ), les cartes linéaires (agissant sur un espace vectoriel V et mappant à un espace éventuellement différent W ) et composition de ces cartes, formes linéaires ou covecteurs (vivant dans un double espace V* et mappage de V vers des scalaires), produits internes (à partir de V × V en scalaires), produits tensoriels entre vecteurs (ie kron ). Produit externe que je préfère considérer comme un produit tenseur entre un vecteur et un covecteur.

L'isomorphisme entre les vecteurs et les formes linéaires est particulièrement intéressant pour ce problème si un produit interne est défini, c'est-à-dire que pour chaque f mappant des vecteurs v aux scalaires, il existe un w tel que f(v) = dot(w,v) pour tout v . Les covecteurs peuvent cependant exister sans référence aux produits internes (par exemple le gradient d'une fonction multidimensionnelle).

Pour représenter tous ces objets sur un ordinateur, vous choisissez généralement une base et pouvez ensuite représenter la plupart de ces choses à l'aide de l'algèbre matricielle. Autrement dit, vous n'avez besoin que de la multiplication et de la transposition de matrices si vous êtes prêt à être flexible avec les dimensions de singleton de fin (vecteurs comme matrices nx1, scalaires comme matrices 1x1, etc.). Cela a été le point de vue de Matlab, ainsi que la façon dont de nombreux livres et articles sont écrits, en particulier en algèbre linéaire numérique.

Dans ce cas, l'isomorphisme précité des vecteurs vers les covecteurs (en supposant implicitement le produit intérieur euclidien) correspond à prendre la transposée (conjugué hermitien dans le cas complexe) de la représentation matricielle d'un vecteur. Cependant, au sens abstrait, il n'y a pas de notion de transposition d'un vecteur, c'est probablement pourquoi il n'est pas défini dans Fortran, comme le précise @GaborOszlanyi . Seule la transposée d'une application linéaire est définie (cela ne nécessite pas de produit interne et sa représentation matricielle correspond à la matrice transposée même dans le cas complexe), ainsi que l'adjoint d'une application linéaire (cela nécessite un produit interne) .

En raison de l'importance de la stabilité des types dans le code Julia, l'approche des «seules matrices» de Matlab (avec des dimensions de singleton de fin flexibles) ne fonctionne pas bien. Mais nous ne voulons pas non plus passer au cadre entièrement abstrait et continuer à travailler dans le cadre typique (produits intérieurs euclidiens, mappage trivial des vecteurs aux covecteurs, ...). Étant donné qu'à la fin nous écrivons du code et voulons utiliser des caractères ASCII, nous devons extraire autant que possible les symboles * , ' et .' . Heureusement, c'est là que l'envoi multiple est utile et conduit aux différentes propositions énoncées ci-dessus. J'ai fait un tableau à ce sujet, c'est-à-dire comment les opérations d'algèbre linéaire abstraite se traduiraient en méthodes julia spécifiques (pas en fonctions).

En guise de note finale à @GaborOszlanyi , je ne trouve toujours pas de place pour v*A dans tout cela. Cela peut être standard dans les champs où les vecteurs sont par défaut désignés comme des matrices de lignes, mais personnellement, je pense que c'est un choix étrange. Si les applications linéaires f et g agissent comme f(v) = v*A et g(v) = v*B , alors cela signifie que g(f(v)) = (g ◦ f)(v) = v*A*B qui est impair depuis l'ordre de composition est interchangé. Le cas échéant, je pourrais interpréter cela comme un produit interne incomplet, comme dans l'avant-dernière ligne de la table liée.

Votre résumé est profond et convaincant.
Merci de l'expliquer si bien.

J'ai juste deux questions restantes:

  • Quels changements se produiront réellement chez Julia à la suite de cette discussion approfondie?
  • Pourquoi Fortran a-t-il implémenté v*mx en matmul ?

Il y a deux problèmes exposés par ce problème:

R. Les mathématiciens appliqués sont associés à la notation Householder, qui utilise implicitement deux isomorphismes naturels:

  1. Un vecteur de longueur N est indiscernable d'une _ matrice de colonnes_ Nx1, puisque les matrices Nx1 forment un espace vectoriel. ("columnify", ▯)
  2. Une matrice 1x1 est indiscernable d'un nombre scalaire, car les matrices 1x1 peuvent être définies avec toutes les propriétés algébriques des scalaires. ("scalariser", ■)

Le problème est qu'aucun de ces isomorphismes n'est naturel à exprimer dans les types de Julia et que les deux impliquent des vérifications au moment de l'exécution de la forme du tableau. Les règles de dimension de singleton de fin de MATLAB peuvent être considérées comme une implémentation de ces deux isomorphismes.

B. Un tableau à deux dimensions peut être défini de manière récursive comme un tableau de tableaux. Cependant, une matrice est une ligne de colonnes ou une colonne de lignes, mais jamais une ligne de lignes ou une colonne de colonnes. Le fait que les matrices ne puissent pas être définies de manière récursive met en évidence les différentes structures tensorielles des matrices et des tableaux à n dimensions. À proprement parler, les tableaux multidimensionnels sont un type de tenseur très limité et manquent de la machinerie complète nécessaire pour mettre en œuvre ce dernier. Parce qu'une contraction à proprement parler est une opération sur un appariement d'un espace vectoriel avec son double, il est abusif de parler de contraction des indices de tableaux multidimensionnels, qui n'invoquent jamais le concept d'espace vectoriel dual. La plupart des gens ne veulent _pas_ la machine complète, ce qui nécessite de s'inquiéter des index co- / contravariants ou haut / bas de nature ligne / colonne. Au lieu de cela, la plupart des gens veulent que les tableaux soient de vieux conteneurs simples, totalement contravariants dans toutes les dimensions, _ sauf en deux dimensions_, où la plupart des utilisateurs veulent penser aux tableaux à deux dimensions comme ayant l'algèbre des matrices (qui sont des tenseurs descendants), et jamais les tenseurs bas-bas, haut-haut ou haut-bas. En d'autres termes, les tableaux à deux dimensions veulent être casés spécialement.


Je pourrais toujours être persuadé du contraire, mais voici ma proposition actuelle en 3 parties:

a) Interdire entièrement la transposition des vecteurs, obligeant les utilisateurs à convertir explicitement les vecteurs en matrices de colonnes afin d'écrire des expressions de style Householder comme u'v , u*v' , u'*A*v et u'*A*v/u'v . Toutes ces expressions peuvent être construites à partir de seulement trois opérateurs unaires et binaires élémentaires: matmul, matrice transposée et division matricielle. En revanche, si u et v sont de vrais vecteurs, alors il n'est pas possible de donner des sous-expressions comme u' ou u'*A signifiant sans introduire un TransposedVector spécial

Une limitation de (a) serait que toutes les expressions de style Householder généreront des matrices 1x1 au lieu de vrais scalaires (ce qui est toujours une énorme amélioration par rapport à u'v renvoyant un vecteur 1), donc une expression comme (u'*v)*w ne fonctionnerait toujours pas. En pratique, je ne pense pas que des expressions de «triple produit» comme celle-ci se produisent souvent.

b) Introduisez une notation alternative pour les opérations analogues sur les vecteurs, telles que

  • u ⋅ v = scalarize(columnify(u)'*columnify(v)) pour le produit interne (dot)
  • u ⊗ v = columnify(u)*columnify(v)' pour le produit externe (Kronecker)
  • A(u, v) = scalarize(columnify(u)'*A*columnify(v)) , une ancienne notation pour la forme bilinéaire
  • A(u) = A(u, u) pour la forme quadratique

Les expressions en (b) diffèrent de leurs homologues en (a) en scalarisant automatiquement les expressions pour le produit interne et les formes bilinéaires / quadratiques, évitant ainsi la formation de matrices 1x1.

c) Rendre les matrices 1x1 convertibles en véritables scalaires, de sorte que le code comme

M = Array(Int, 1, 1, 1)
a::Int = M

pourrait fonctionner. Tout ce qui serait nécessaire est de faire la vérification à l'exécution de la taille du tableau avec quelque chose comme:

function convert{T}(::Type{T}, A::Array{T,N})
    if length(A) == 1
        return A[1]
    else
        error()
    end
end

Cette proposition est une petite modification de ce qui a été proposé par Folkmar Bornemann il y a environ deux ans. Lorsque nous l'avons essayé, le coût de la vérification de l'exécution n'était pas très élevé, et il ne serait invoqué que sur une affectation forcée par type (qui est en fait un appel convert déguisé), pas une affectation générale.

@jiahao , la lisibilité de ce message est limitée par les caractères difficiles à rendre. Cela n'a même pas l'air parfait sur OS X, qui a généralement assez complètement Unicode dans ses polices.

@jiahao , je pourrais certainement être d'accord avec la plupart / tout cela, même si je voudrais contester deux points:

sans introduire un type spécial TransposedVector , qui par conséquent oblige les tableaux multidimensionnels à se soucier des indices haut / bas dans leur structure tenseur, ce qui semble être un prix trop élevé à payer.

Cela ne me semble une conséquence logique que si vous voulez faire de TransposedVector un sous-type de la hiérarchie AbstractArray , ce que je supposais n'était pas le plan (contrairement à un type LazyTranspose qui vraiment est juste un autre type de AbstractMatrix ).

u ⊗ v pour le produit externe (Kronecker)

Si l'objectif est de ne pas s'appuyer sur des isomorphismes implicites et de séparer proprement les opérations matricielles des opérations vectorielles et des produits plus abstraits, je pense que cela échoue pour la raison suivante (je ne suis pas sûr de l'accord universel sur les définitions mathématiques suivantes):

  • Le produit Kronecker A ⊗ B est une opération définie sur des matrices, pas sur des vecteurs.
  • Par conséquent, au lieu de lire u ⊗ v comme le produit tensoriel de deux vecteurs, cela donnerait naissance à un tenseur bidimensionnel de type down down. La seule façon d'obtenir une «matrice» appropriée serait de prendre le produit tensoriel d'un vecteur avec un covecteur. Mais comme on veut éviter d'introduire ces objets, il semble qu'il soit impossible d'obtenir le résultat de cette opération avant de mettre en colonne les deux vecteurs impliqués.
  • Un troisième nom pour u ⊗ v est le produit externe qui est généralement mal défini, et je ne suis pas sûr qu'il existe une définition rigoureuse acceptée en termes de haut et de bas. Certaines sources affirment qu'il équivaut au produit tensoriel de deux vecteurs, d'où le point précédent. Si à la place vous acceptez une définition où «produit extérieur» signifie également mapper implicitement le deuxième vecteur à un covecteur afin d'obtenir un tenseur descendant, alors il n'y a pas de problème.

Au lieu de cela, la plupart des gens veulent que les tableaux soient de vieux conteneurs simples, totalement contravariants dans toutes les dimensions, sauf en deux dimensions,

Avons-nous envisagé l'option nucléaire? Nous commençons l'algèbre linéaire en supprimant complètement les typealias pour AbstractVector et AbstractMatrix et en les remplaçant par:

abstract AbstractVector{T} <: AbstractArray{T,1}
abstract AbstractMatrix{T} <: AbstractArray{T,2}

# and we could introduce:
abstract AbstractCoVector{T} <: AbstractArray{T,1}

Je comprends qu'il y a beaucoup de retombées, mais nous pourrions nous retrouver avec une séparation nette entre les tableaux de stockage multidimensionnels et l'algèbre linéaire. Nous n'avons pas à implémenter l'algèbre tensorielle multidimensionnelle complète, les espaces vectoriels doubles, etc. Nous devons simplement implémenter les bits que beaucoup de gens veulent: vecteurs, covecteurs et matrices. Nous obtenons des produits internes, des produits externes, des produits covector-matrice, des produits matrice-vecteur et des produits matrice-matrice. La transposition est définie sur tout ce qui précède. Nous pouvons avoir des implémentations de AbstractMatrix qui utilisent vraiment un tableau 1D de tableaux 1D (pas l'implémentation par défaut, bien sûr). Nous n'avons pas besoin d'utiliser la notation Householder (qui à mon humble avis est l'une des faiblesses de MATLAB!) Mais nous pouvons toujours obtenir toutes les commodités de l'algèbre linéaire.

Je suis un peu nerveux à l'idée de suggérer cela, mais je crois que cela permettra à Julia d'imiter le modèle "correct" de ce que la plupart des gens apprennent par exemple en algèbre linéaire de niveau universitaire de première année, sans avoir besoin de se replier sur les conventions de Householder. Faire une distinction claire peut également faciliter le déplacement de Base.LinAlg dans un package "bibliothèque standard", ce qui, je crois, est un objectif à long terme pour Julia? Cela correspond également bien à l'idée qu'il y aura une nouvelle chose de type liste redimensionnable 1D qui viendra avec les nouveaux changements Buffer et l'implémentation native de Array , donc ce type de "liste" générique peut remplacer Vector pour la plupart des parties principales de Julia et nous allons charger le paquet LinAlg assez tard tout en ayant Array et "list" définis assez tôt.

Il existe de nombreux algorithmes qui ont déjà été "simplifiés", et qui sont exprimés en termes de tableaux de Fortran et non en termes d'algèbre linéaire propre. Julia doit être capable de mettre en œuvre ces algorithmes sans que les gens aient à redécouvrir (ou contraindre) une structure d'algèbre linéaire au-dessus de tableaux multidimensionnels.

Dans ma ligne de travail, une algèbre linéaire appropriée qui mappe des tenseurs et des indices co / contravariants, etc. sur des tableaux de Fortran est probablement la meilleure. Pour que cela fonctionne - et pour ne pas confondre les gens - je laisserais seuls les termes «vecteur», «matrice» et «tableau», en les gardant à un niveau bas, et j'utiliserais d'autres termes (plus sophistiqués?) Pour tout ce qui est de niveau supérieur . Ledit niveau supérieur devrait également faire abstraction des options de stockage, soit via des types abstraits, soit via une paramétrisation. Peut-être qu'un préfixe LA est le moyen d'exprimer ceci:

LA.Vector
LA.CoVector
LA.Tensor{... describing co/contravariance of indices ...}

Les vecteurs et matrices sont alors uniquement utilisés pour le stockage. Au niveau bas, la transposition est gérée manuellement (comme dans BLAS); au plus haut niveau, il est géré automatiquement.

C'est proche de la suggestion de

@eschnett Je pense que plus tôt dans ce fil, il a été décidé que l'algèbre multi-linéaire allait être laissée pour les packages, plutôt que pour la base Julia. Je pense que beaucoup de ces idées comme vous suggérez pourraient être étoffées dans un nouveau package qui traite les tenseurs, les espaces vectoriels, la co / contra-variance et ainsi de suite dans le système de types, quelque peu différent du package _TensorOperations.jl_ qui fournit des fonctions pratiques pour multiplier les tableaux comme s'ils étaient des tenseurs. Je pense que ce serait difficile à développer mais potentiellement une abstraction valable!

Quant à laisser Matrix et Vector seuls - eh bien peut-être que les définitions actuelles sont parfaites, ou peut-être qu'il y a quelque chose de mieux que nous pouvons faire pour les gens qui veulent multiplier des matrices et transposer des vecteurs, en utilisant des mathématiques standard notation. Je suppose que cela peut ajouter une petite courbe d'apprentissage aux nouveaux utilisateurs, même si j'avais espéré que si le système était évident et éloquent et une amélioration sur d'autres langues, il devrait être facile à apprendre.

Notez qu'un espace vectoriel complexe non euclidien général V a 4 espaces associés: V , conj(V) , dual(V) et conj(dual(V)) . Donc, un tenseur général qui a 4 types d'indices (généralement désignés comme haut ou bas, barré ou non barré). Dans un espace euclidien complexe (par exemple la mécanique quantique), dual(V) ≡ conj(V) et conj(dual(V)) = V . Dans un espace réel (non euclidien) (par exemple la relativité générale), V ≡ conj(V) . Dans les deux derniers cas, seuls des indices haut et bas sont nécessaires.

Dans un espace euclidien réel (la plupart des applications?), Toutes sont équivalentes, et les tableaux simples de Julia suffisent à représenter des tenseurs. Donc au moins pour les nombres réels, les deux opérations suivantes permettent de construire une algèbre tensorielle complète et cohérente.

  • contrat / produit interne , qui pourrait être généralisé à des tableaux arbitraires de rang N utilisant la règle: contracter le dernier index du premier tableau avec le premier index du second (ou numpy dot convention, bien que je trouve cela moins intuitif), tel que A ∙ B renvoie un tableau de rang M+N-2 si A et B avait rang M et rang N .
  • produit tensoriel : A ⊗ B renvoie un tableau de rang N+M

Spécialisé jusqu'aux vecteurs v , w et matrices A , B , cela permet d'écrire tout ce qui suit:

  • produit intérieur vectoriel v ∙ w -> renvoie exceptionnellement un scalaire au lieu d'un tableau de rang 0
  • multiplication vectorielle matricielle A ∙ v
  • multiplication matricielle matricielle A ∙ B
  • tenseur / produit extérieur v ⊗ w
  • multiplication de matrice de covecteur (=== vecteur) v ∙ A

Alors que l'on peut vouloir utiliser * pour , le piège est que la définition ci-dessus de n'est pas associative: (A ∙ v) ∙ w ≠ A ∙ (v ∙ w) car ce dernier ne le ferait même pas être défini.

Mais là encore, ce n'est que si vous êtes prêt à ignorer les tableaux complexes.

Quant à une implémentation complètement générale des tenseurs, j'ai commencé (et abandonné) cela il y a longtemps, avant qu'il y ait des tuples alloués par pile, des fonctions générées et tous ces autres goodies qui le rendraient probablement plus faisable aujourd'hui.

Approche intéressante, @Jutho. Cela semble assez intuitif. Je _ suppose_ que ces définitions pourraient être ajoutées indépendamment de ce que nous faisons avec * et ' , et je serais d'accord.

Mais là encore, ce n'est que si vous êtes prêt à ignorer les tableaux complexes.

Inséré manuellement conj() corrige cela. Et je ne pense pas que Julia veuille ignorer les matrices complexes, n'est-ce pas?

J'ai remarqué quelques autres choses sur le fait d'avoir AbstractMatrix comme sous-type spécial plutôt que comme alias de type:

matrix[:,i] -> AbstractVector{T}
matrix[i,:] -> AbstractCoVector{T}
array_2d[:,i] -> AbstractArray{T,1}
array_2d[i,:] -> AbstractArray{T,1}

C'est plutôt cool - nous pouvons obtenir des vecteurs de colonne et de ligne en indexant une matrice. S'il ne s'agit que d'un conteneur de stockage 2D, nous obtenons une matrice de stockage 1D. Devrait couvrir tous les cas d'utilisation! Et il obéit toujours à l'interface AbstractArray avec les règles de découpage APL puisque à la fois AbstractVector{T} <: AbstractArray{T,1} et AbstractCoVector{T} <: AbstractArray{T,1} .

Un fait "intéressant" est

array_3d[:,:,i] -> AbstractArray{T,2}
matrix(array_3d[:,:,i]) -> `AbstractMatrix {T}

vous devrez donc l'envelopper manuellement comme une matrice si vous voulez faire une multiplication matricielle avec le résultat. Est-ce un plus ou un moins, je ne sais pas? Mais cela semble être une complication potentielle et touche à ce que @eschnett a mentionné pour faciliter la tâche aux utilisateurs d'autres langages qui combinent le stockage et l'algèbre linéaire.

C'est peut-être une question idiote, mais @jutho a mentionné plus tôt que l'écriture de code était limitée par ASCII. Pourquoi diable nous limitons-nous toujours à un jeu de caractères 7 bits développé en 1963 et mis à jour pour la dernière fois en 1986 (il y a 30 ans)? C'était une époque où le célèbre 640 Ko était la RAM maximale disponible sur un PC en 1981. Sur le marché informatique actuel, nous avons maintenant couramment des processeurs 64 bits avec 32 Go de RAM en vente (50 000 fois le maximum précédent) et nous sommes loin d'être proches de la limite théorique pour les processeurs 64 bits. Alors, pourquoi nous limitons-nous encore à un jeu de caractères développé il y a 40 ans?

Nous pouvons utiliser l'unicode, gardez simplement à l'esprit que, malheureusement, la taille du clavier n'a pas augmenté d'un facteur similaire au cours des 40 dernières années, ni le nombre de doigts d'une personne normale (au cours des 10000 dernières années).

IMHO, ASCII doit être mis au repos. Un clavier mathématique spécial pour une programmation rapide de symboles mathématiques est en fait une bonne idée! Y a-t-il une bonne raison de ne pas utiliser plus de symboles fournis avec UTF? Pouvez-vous justifier la douleur d'essayer d'extraire tout ce que vous pouvez de l'ASCII?

@ esd100 : Veuillez ne pas utiliser ce problème GitHub pour de telles discussions hautement spéculatives.

@Johnmyleswhite. Je ne sais pas ce que vous entendez par «spéculatif». Êtes-vous sûr que c'est le mot que vous vouliez utiliser? Je pensais que l'utilisation de l'ASCII par rapport à autre chose était pertinente pour la discussion, car il semble que le langage, les opérateurs et les fonctionnalités soient liés aux symboles utilisés. Je ne suis en aucun cas un expert, mais cela semble être un problème pour discuter, ou au moins être clair, lié à la fonctionnalité abordée.

Je veux dire, y a-t-il une raison pour laquelle nous ne pouvons pas définir le langage comme nous le voulons (avec les objectifs fondamentaux à l'esprit: facilité d'utilisation, rapidité)? L'utilisation de symboles / mots spéciaux ne rendrait-elle pas la langue plus riche et plus belle?

@ esd100 veuillez noter que, par exemple, le commentaire le plus récent de @yuyichao convient que nous pouvons utiliser unicode. Il y a d'autres propositions concernant l'introduction de plus d'opérateurs uniquement Unicode (par exemple le symbole de composition pour composer des fonctions, # 17184). Je ne pense pas que les gens soient en désaccord avec vous (bien que nous ayons des considérations pratiques à garder à l'esprit), mais si vous avez des suggestions spécifiques sur un sujet, veuillez nous en informer.

Je suis un peu confus par ce nouveau comportement dans v0.5 qui, je crois, est né de cette discussion:

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.

Comment créer un vecteur de ligne (non numérique) à partir d'une compréhension de liste? Ce doit être un vecteur de ligne. (Est-ce que ce serait mieux en tant que problème séparé ou discuté ailleurs? Je ne savais pas où publier ...)

Vous pouvez utiliser reshape: reshape(v, 1, length(v)) . Peut-être que cela devrait également être mentionné dans l'avertissement de désapprobation? Je pense que l'idée est que la transposition est une opération mathématique et ne devrait donc être définie que pour les vecteurs / matrices mathématiques.

Il est mentionné dans le depwarn: permutedims(x, [2, 1]) .

permutedims ne fonctionne pas pour les vecteurs. Voir ce tout nouveau numéro: # 18320

Je pense que l'idée est que la transposition est une opération mathématique et ne devrait donc être définie que pour les vecteurs / matrices mathématiques.

Cela n'a aucun sens pour moi. Est-ce à discuter? Je préférerais vraiment transposer pour travailler sur des tableaux non numériques à moins qu'il n'y ait une très bonne justification.

Je pense que l'idée est que la transposition est une opération mathématique et ne devrait donc être définie que pour les vecteurs / matrices mathématiques.

Cela n'a aucun sens pour moi. Est-ce à discuter? Je préférerais vraiment transposer pour travailler sur des tableaux non numériques à moins qu'il n'y ait une très bonne justification.

Je pense qu'il est relativement complexe de satisfaire ce que vous voulez et d'avoir un sens pour les mathématiques. Dans un sens mathématique, la transposition est souvent définie en permutant les espaces vectoriels et leurs doubles espaces d'un vecteur ou d'une matrice (enfin, il y a aussi une complication avec transpose vs conjugate transpose). Pour une matrice M de nombres réguliers, nous avons la transposition comme permutedims(M, (2,1)) , mais en général, vous pouvez définir par exemple une matrice "bloc" en termes de sous-matrices comme ceci:

M = [A B;
     C D]

A etc sont des matrices elles-mêmes. Je crois comprendre que Julia aime avoir

M' = [A' C';
      B' D']

ce que vous feriez en utilisant les mathématiques du stylo et du papier et qui est donc une «syntaxe souhaitable».

Cela signifie que les éléments d'une matrice doivent accepter ' et .' . Pour les nombres, ils sont définis respectivement comme une conjugaison complexe et des no-ops. IMHO Je pense que c'est un jeu de mots de commodité, mais cela fonctionne et, plus important encore, il est implémenté avec des règles _simple_ ("transposer est récursif" - par opposition à avoir besoin de BlockMatrix classes et ainsi de suite). Mais ce jeu de mots sur la transposition a été supprimé des types non numériques dans 0.5, car cela n'a pas beaucoup de sens. Quelle est la transposition d'un Symbol ?

Si vous avez des _data_, pas des nombres, alors utiliser permutedims est parfaitement bien défini dans sa signification et son comportement: il est non récursif. L'utilisation d'un jeu de mots mathématique comme .' peut vous faire économiser quelques caractères - mais cela rendra un _lot_ plus logique pour quelqu'un d'autre (qui pourrait ne pas être très familier avec les mathématiques ou MATLAB) pour lire votre code si vous utilisez reshape et permutedims si nécessaire. _Pour moi, c'est le_ point le plus _important_.

À mon humble avis, ce très long problème consiste à créer une interface mathématiquement cohérente pour l'algèbre linéaire, et il me semble naturel que le résultat soit moins de jeux de mots mathématiques pour les tableaux de données.

Merci pour la réponse réfléchie. Je suis toujours assez fortement en désaccord

Mais ce jeu de mots sur la transposition a été supprimé des types non numériques dans 0.5, car cela n'a pas beaucoup de sens. Quelle est la transposition d'un symbole?

Je ne suis pas sûr que définir la transposition d'un Symbol comme un no-op ait moins de sens que de définir la transposition sur un nombre réel. Je comprends que le "jeu de mots" est pratique, mais il semble que la "bonne" chose à faire serait de définir la transposition uniquement pour les vecteurs et les matrices et d'avoir transpose(A::Matrix{Complex}) apply conj dans le cadre de son exécution.

L'utilisation d'un jeu de mots mathématique comme .' peut vous faire économiser quelques caractères - mais il sera beaucoup plus logique pour quelqu'un d'autre (qui n'est peut-être pas très familier avec les mathématiques ou MATLAB) de lire votre code si vous utilisez remodeler et permutés si nécessaire. Pour moi, c'est le point le plus important.

Je suis d'accord que .' doit être utilisé judicieusement et qu'un appel plus explicite à transpose est souvent mieux. Je pense que reshape et permutedims peuvent nécessiter une charge cognitive non négligeable pour lire et coder en premier lieu. Soyez honnête, ce qui est plus rapide à analyser:

transpose([ f(x) for x = 1:length(A) ])
reshape([ f(x) for x = 1:length(A) ], 1, length(A))

Même dans des cas simples comme celui-ci, vous devez rebondir du début de la ligne (pour lire reshape ) jusqu'à la fin (pour lire length(A) ) pour comprendre ce qui se passe. (Vous revenez probablement au milieu pour comprendre pourquoi length(A) est là en premier lieu.)

Pour moi, le plus gros problème est que si je suis une nouvelle Julia (ce qui signifie que j'ai probablement utilisé numpy et MATLAB auparavant) et que je vois que cela fonctionne:

[ x for x = 1:10 ]'

Je vais naturellement supposer que la même chose fonctionnera pour les tableaux de chaînes, de symboles, etc. Je ne vais pas lire de longues discussions en ligne pour comprendre la sémantique de .' - je suis va essayer certaines choses et généraliser / déduire par l'expérience passée. Peut-être qu'un meilleur message d'erreur serait utile ici.

Dans l'ensemble, je ne vois pas en quoi le maintien de la transposition sans opposition sur Symbol et d'autres entrées non numériques interfère avec le joli cadre mathématique proposé ici (un objectif louable!). Mais garder le no-op semble inoffensif.

Mais garder le no-op semble inoffensif.

Vous ne pouvez vraiment rien définir de manière significative pour Any car tout est un sous-type de Any . La définition transpose(x)=x est tout simplement fausse pour tout type de matrice et pour éviter certaines des erreurs silencieuses, nous avons dû ajouter ces définitions . C'est donc un compromis entre la commodité d'autoriser la syntaxe relativement étrange ' pour une opération complètement non mathématique et d'éviter les erreurs silencieuses.

Je ne suis pas sûr que définir la transposition d'un symbole comme un non-op ait moins de sens que définir la transposition sur un nombre réel. Je reçois que le « jeu de mots » est pratique, mais il semble que la « bonne » chose à faire serait d'avoir transposent que défini pour les vecteurs et matrices et ont transpose(A::Matrix{Complex}) applique conj dans le cadre de son exécution.

Je ne suis pas complètement en désaccord, mais nous aurions besoin d'implémenter une sorte de BlockMatrix ou d'avoir des méthodes spéciales définies pour Matrix{M<:Matrix} . Je ne sais pas si c'est une idée populaire ou non? (C'est une question sérieuse pour ceux qui ont suivi car cela pourrait simplifier certaines de ces questions connexes).

Soyez honnête, ce qui est plus rapide à analyser:

transpose([ f(x) for x = 1:length(A) ])
reshape([ f(x) for x = 1:length(A) ], 1, length(A))

La seconde, parce que je ne me rapporte pas / n'aime pas la transposition actuelle des vecteurs de Julia (clairement, _Je prends les vecteurs transposés_ trop _sérieusement_ :)) Si je devais faire la seconde, j'écrirais verbosement rowvec = reshape(colvec, (1, n)) , ou probablement [f(x) for _ = 1:1, x = 1:n] pour forcer la compréhension à créer la bonne forme pour commencer, ou si vous aimez vraiment .' alors map(f, (1:n).') et f.((1:n).') fonctionnent également.

c'est un compromis entre la commodité d'autoriser la syntaxe relativement étrange 'pour une opération complètement non mathématique et d'éviter les erreurs silencieuses

Si cela causait des erreurs silencieuses et d'autres problèmes, alors je suppose que je vais probablement perdre cet argument. (Je ne vois pas pourquoi cela causerait des erreurs - mais je vous crois.) D'un autre côté ...

nous aurions besoin d'implémenter une sorte de BlockMatrix ou d'avoir des méthodes spéciales définies pour Matrix {M <: Matrix}

Je me trompe peut-être, mais je pense que vous n'avez qu'à faire le deuxième, ce qui me semble être une option raisonnable. Mais cela commence à dépasser mon salaire.

[f (x) pour _ = 1: 1, x = 1: n]

J'ai oublié ça! C'est probablement ce que je finirai par faire. Dans l'ensemble, je ne suis toujours pas d'accord avec vos goûts pour le code lisible, mais à chacun le sien! ¯\_(ツ)_/¯

clairement, je prends trop au sérieux les transpositions vectorielles

Oui. Je le pense. 😉

Cela (https://github.com/JuliaLang/julia/issues/16790) rendrait également l'utilisation de reshape à la place de transpose légèrement plus acceptable pour moi.

Je me trompe peut-être, mais je pense que vous avez juste besoin de faire la deuxième (modifier: avoir des méthodes de transposition spéciales définies pour Matrix{M<:Matrix} ), ce qui me semble être une option raisonnable.

Malheureusement, nous revenons maintenant à la distinction entre les données et l'algèbre linéaire. Vous voulez que les matrices de blocs d'algèbre linéaire aient une transposition récursive, mais les tableaux de données 2D génériques de tableaux de données 2D ne doivent Matrix{T} et Array{T,2} sont la même chose, nous ne pouvons pas fais ça.

Cela (# 16790) rendrait également l'utilisation de remodeler au lieu de transposer un peu plus agréable au goût.

Vrai!!

Vous voulez que les matrices de blocs d'algèbre linéaire aient une transposée récursive, mais les tableaux de données 2D génériques de tableaux de données 2D ne doivent pas avoir de transposée récursive

Cela ne semble pas être quelque chose que je voudrais évidemment ... Pourquoi ne pas avoir simplement deux fonctions différentes pour gérer ces différents types de transpositions? Je suppose que j'ai manqué le mémo sur la distinction très stricte entre les objets d'algèbre linéaire et les objets de données.

La lecture de tout ce fil semble être une tâche ardue, mais je devrais peut-être le faire avant de commenter davantage. Je ne veux pas ajouter de bruit non informé.

Je suppose que j'ai manqué le mémo sur la distinction très stricte entre les objets d'algèbre linéaire et les objets de données.

Il n'y a pas de distinction stricte entre les objets d'algèbre linéaire et l'objet de données.
Pas dans la mise en œuvre actuelle, ni dans l'utilisation idéale.

Si tel était le cas, la modification de la taille (avec push! ) ou même des valeurs des objets d'algèbre linéaire ne serait pas prise en charge (et ces utilisateurs utiliseraient StaticArrays.jl etc.), et broadcast ne le ferait que être pris en charge sur les objets d'algèbre linéaire.
Les objets de données seraient modifiables, extensibles et prendraient en charge map , (et reduce , et filter ) mais pas broadcast .

Mais nous ne vivons
Ainsi ce 2,5 ans, 340 fils de commentaires.


Re la transposition no-op .
Nous pourrions ajouter, très haut dans la hiérarchie des types, un type abstrait Scalar et Nonscalar .
Scalars tous les replis vers une transposition sans opposition.
Nonscalars n'ont pas de solution de rechange, (mais pour le moment, revenez à un avertissement d'obsolescence)

Les nombres, les caractères, les chaînes et peut-être même les tuples seraient Scalar et auraient une transposition sans opposition définie. Certains scalaires, par exemple les nombres complexes, écraseraient cette définition de transposer.

Les tableaux (matrice, vecteurs et autres) seraient des sous-types de Nonscalar et auraient une transposition récursive.

Je ne sais pas si j'aime ça ou pas.

La récente vague de messages réorganise les discussions sur les types _matrix transpose_ et "scalar" dans # 18320 # 13171 # 13157 # 8974.

Je voudrais vraiment dissiper l'idée que la solution de secours transpose(x)=x no-op est inoffensive. Dans mon esprit, un repli devrait toujours produire le résultat correct, juste plus lentement qu'un algorithme optimisé. C'est dangereux lorsque la méthode de secours calcule silencieusement la mauvaise réponse, car cela signifie que la solution de secours n'a pas la sémantique correcte: transpose(x) signifie des choses différentes selon le type de x .

Le repli de transposition sans opposition n'est pas seulement faux pour les matrices de matrices, mais il est faux pour tous les objets de type matrice qui ne sont pas des sous-types de AbstractMatrix (ce qui signifie par # 987 qu'ils ont des entrées explicitement stockées, _pas_ qu'ils ont l'algèbre des matrices). Voici un exemple d'enfant d'affiche (dont nous en avons pas mal):

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

Cet exemple montre que ce n'est pas parce que quelque chose est un sous-type de Any que vous pouvez supposer qu'il s'agit d'un scalaire et qu'il a une transposition sans opposition. Cela illustre également pourquoi le problème du parent dans l'OP est si difficile à résoudre. Pour un objet Q non-tableau de type matrice, Q' n'a pas de signification réelle en tant qu'opération de tableau mais il a une signification algébrique non ambiguë: les expressions comme Q'A sont parfaitement bien définies. D'autres personnes qui travaillent avec des tableaux et non avec de l'algèbre linéaire veulent simplement des permutés non récursifs et ne se soucient pas des non-tableaux de type matrice. Néanmoins, le fait demeure que vous ne pouvez pas avoir une orthographe cohérente de la sémantique algébrique et de permutation d'axes pour tous les types.

Peut-être que je suis dense, mais j'aurais pensé que la comparaison serait la suivante:

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

Écraser transpose de cette manière semble permettre transpose([ :x _=1:4 ]) - c'est-à-dire ce que j'ai publié. J'aurais pensé que tant que vous implémenteriez transpose / ctranspose correctement pour tout ce qui en a besoin (par exemple QRCompactWYQ ) la solution de secours ne serait jamais appelée (depuis un appel plus spécifique peut être fait).

Votre code n'appelle pas la méthode ctranspose vous avez écrite (vous pouvez le vérifier avec @which ). Il appelle une méthode de secours différente dans Julia v0.5 (qui n'existe pas dans la v0.4) qui fait essentiellement ctranspose(full(Q)) . Cette autre solution de repli est correcte, mais va à l'encontre de la raison même pour laquelle nous avons ce type de Q sophistiqué (de sorte que la multiplication avec elle puisse être faite avec précision). Mon commentaire selon lequel les solutions de secours devraient être correctes est toujours valable.

Oui, tant que vous implémentez correctement la transposition pour tout ce qui
en a besoin, le repli ne fait bien sûr aucun mal. Le mal c'est que tu ne
obtenir une erreur sans méthode si vous oubliez de faire cela, mais silencieusement le mauvais
résultat.

Merci @toivoh qui a fait cliquer pour moi. J'apprécie vraiment les explications.

Mais si vous définissez une fonction de secours transpose(X::AbstractMatrix) et transpose(X::AbstractVector) , vous obtiendrez probablement toujours le résultat correct (même s'il était lent ... en appelant full par exemple) non? Et vous pouvez toujours écrire une fonction plus spécialisée pour le faire mieux / plus rapidement. Alors transpose(::Any) ne devrait jamais causer d'erreurs silencieuses autres que les "jeux de mots" mentionnés précédemment (c'est-à-dire lors de la gestion des nombres Complex ... peut-être d'autres cas d'utilisation scalaires que je ne connais pas?)

Pour des raisons historiques QRCompactWYQ <: AbstractMatrix , mais selon # 987 # 10064, cette relation de sous-typage n'est pas correcte et doit être supprimée.

Un autre exemple de non-tableau de type matrice est IterativeSolvers.AbstractMatrixFcn , qui n'est pas un sous-type de AbstractMatrix . Pour ce type, les solutions de secours auxquelles vous faites référence ne seraient jamais distribuées, ce qui était encore une fois mon point principal.

Nous devrions poursuivre cette discussion sur https://github.com/JuliaLang/julia/issues/13171. Le problème concerne en fait principalement les vecteurs avec des éléments de type nombre.

Quelqu'un de "l'algèbre linéaire en équipe" doit intensifier et s'engager à faire quelque chose à ce sujet pour 0.6 ou il va être à nouveau bousculé.

Alors pour redémarrer, quel est le plan réel?

Ma ligne de pensée m'amène à: transpose(v::AbstractVector) = TransposedVector(v)TransposedVector <: AbstractVector . La seule chose sémantique qui distinguera TransposedVector de AbstractVector sera son comportement sous * (et tous les A_mul_B s, \ , / , ...). C'est à dire que c'est un décorateur pour déterminer sur quels indices se contracter sous * (etc ...). La transposition serait un concept d'algèbre linéaire et si vous voulez réorganiser un tableau de "données", reshape et permutedims devraient être encouragés.

La seule chose sémantique qui distinguera TransposedVector de AbstractVector sera son comportement sous *

Donc v'==v mais v'*v != v*v' ? Bien que cela puisse avoir du sens, cela semble également potentiellement déroutant.

Donc v'==v mais v'*v != v*v' ? Bien que cela puisse avoir du sens, cela semble également potentiellement déroutant.

OMI, c'est inévitable (bien que peut-être malheureux).

Le dual d'un vecteur est également un vecteur. La transposition est également toujours une structure de données unidimensionnelle avec des éléments bien définis qui devraient pouvoir obtenir, muter, mapper, réduire, etc.

À moins que nous ne séparions l'algèbre linéaire des tableaux (par exemple, faire de Matrix{T} fois un sous-type et un wrapper immuable de Array{T,2} , avec plus de méthodes (spécifiques à l'algèbre linéaire) définies), alors je ne suis pas sûr qu'il y ait est beaucoup de choix qui est cohérent avec ses propriétés de tableau et d'algèbre linéaire.

La seule question déroutante (pour moi) est de savoir si elle diffuse comme un vecteur, ou sur sa deuxième dimension. Si sa taille est (1, n) alors ce changement dans son ensemble ne fait pas grand chose sauf en affirmant que c'est comme une matrice où la première dimension est connue pour être de longueur 1 . La transposition d'un Vector{T} devrait rester un Array{T,2} (c'est- Matrix dire Vector ( par exemple, nous pourrions avoir v'' === v ).

Est-ce une meilleure idée? Ce serait moins cassant et améliorerait encore la sémantique de l'algèbre linéaire. EDIT: et il se comporte différemment wrt == comme @martinholters l' évoque).

Pour moi, avoir TransposedVector{T <: AbstractVector{Tv}} <: AbstractMatrix{Tv} *) avec size(::TransposedVector, 1)==1 mais transpose(::TransposedVector{T})::T semble être l'approche la plus saine, mais il y a eu tellement de débats qu'il y a probablement un bon argument contre cela?

*) Je sais que c'est syntaxiquement invalide, mais j'espère que la sémantique prévue est claire.

Oui, en jouant avec des idées dans le code, je trouve que je suis d'accord avec vous @martinholters.

J'ai commencé à https://github.com/andyferris/TransposedVectors.jl. Tout cela peut être réalisé avec seulement une petite quantité de piratage de type et de méthodes à partir d'un paquet en dehors de la base, et j'ai rendu un paquet compatible avec Julia 0.5. Si cela se passe bien, nous pourrions peut-être le porter sur Base? (Ou bien apprenez quelques leçons).

Une grande question est de savoir si nous pouvons vivre sans un type CTransposedVector pour une conjugaison complexe, ou comment cela sera géré.

Je n'utiliserais pas un CTransposedVector séparé, car cela combine deux concepts (conjugaison et remodelage) en un seul objet. Il est beaucoup plus facile de conserver ces deux concepts implémentés en tant qu'entités distinctes. Par exemple, avec MappedArrays.jl, c'est aussi simple que

conjview(A) = mappedarray((conj,conj), A)

et vous obtenez également d'excellentes performances.

Bien, merci Tim. Je pensais à / j'espérais quelque chose comme ça - ma seule préoccupation était de savoir comment vous pourriez vous assurer que les deux wrappers apparaissent toujours dans le "bon" ordre, mais je pense que ce que j'ai implémenté jusqu'à présent peut gérer cela. Nous aurions également besoin de prendre en charge BLAS pour toutes ces combinaisons, que j'ai réussi à éviter de toucher jusqu'à présent.

Une belle chose est qu'un changement séparé qui par défaut de conj à conjview serait indépendant de ce changement (je pense qu'ils pourraient tous les deux être bricolés dans des packages indépendants et qu'ils composeront correctement ensemble). Y a-t-il un désir de faire conj une vue? Attendons-nous des inmutables non-isbits en ligne pour rendre ces vues gratuites? Ou pas besoin d'attendre?

@andyferris : Je pense que votre approche est bonne - merci de l'avoir poussée. Si l'implémentation fonctionne bien, nous pouvons simplement utiliser ce code dans Base. Une chose à garder à l'esprit est que nous pouvons également vouloir TransposedMatrix pour https://github.com/JuliaLang/julia/issues/5332. Au-delà des dimensions 1 et 2, je ne pense pas que les transpositions aient du sens, c'est donc un ensemble fini de types.

Maintenant que nous avons continué la conversion, je mentionnerai à nouveau que nous devrions essayer d'éviter un type de vecteur transposé. Cela a été mentionné à quelques reprises ci-dessus, mais je doute que quiconque puisse à nouveau lire tous les commentaires sur ce numéro. L'introduction d'un type de transposition pour les vecteurs complique vraiment les choses pour presque aucun avantage. Si vous pensez vraiment qu'un vecteur a une direction, faites-en une matrice et les choses fonctionneront. Cela n'a pas beaucoup de sens d'avoir des vecteurs d'algèbre linéaire différents des matrices, puis d'introduire un nouveau type d'enveloppe pour que les vecteurs se comportent comme des matrices 1xn .

Actuellement, l'asymétrie est que x' promeut x vers une matrice alors que A*x ne promeut pas le x vers une matrice. Si les opérations d'algèbre linéaire étaient juste pour 2D, alors A*x devrait promouvoir et x'x serait une matrice 1x1 . Alternativement, nous pourrions laisser les vecteurs être des vecteurs et donc aussi A*x être un vecteur mais alors x' devrait être un noop ou une erreur. L'introduction d'un vecteur transposé nécessitera beaucoup de nouvelles définitions de méthodes et le seul avantage semble être que x'x devient un scalaire.

Je pense que la transposition matricielle est très différente. Cela nous permettra de nous débarrasser de toutes les méthodes Ax_mul_Bx et le comportement n'est pas discutable de la même manière que le comportement des transpositions vectorielles.

Je ne vois pas vraiment comment l'introduction d'un type TransposedVector pose plus de problèmes que l'introduction d'un type TransposedMatrix.

Oui, le fort consensus quelque part au milieu de cet opus était que la transposition vectorielle est bizarre, et que, si elle est implémentée, elle ne devrait certainement pas être un sous-type de AbstractArray - je refuserais même size entièrement. Mon résumé ci - conj sur le tableau au lieu de l'inclure comme type paramètre).

Mais il y avait un appel encore plus fort pour que la transposition vectorielle soit simplement une erreur. Si tel était le cas, je pense qu'il pourrait en fait vivre dans un package.

Faire en sorte que la transposition du vecteur soit une erreur ressemble à jeter le bébé avec l'eau du bain. Ne pas pouvoir écrire v' pour transposer un vecteur serait vraiment très ennuyeux. Je ne comprends pas vraiment ce qui est si mauvais d'avoir un type TransposedVector qui se comporte comme une matrice de lignes en dehors de la façon dont il se multiplie avec des vecteurs et des matrices.

Cela dépend du nombre de comportements que vous souhaitez traiter avec une transposition vectorielle. Si vous voulez simplement v'' == v , alors c'est ok pour typeof(v') <: AbstractMatrix . Mais si vous voulez les autres choses dont nous avons parlé dans ce fil, comme typeof(v'v) <: Scalar ou typeof(v'A) <: AbstractVector , alors il doit s'agir d'une bête différente, plus compliquée.

L'idée de faire de TransposedVector une sorte de Vector semble être à l'origine de nombreux problèmes. Il semble que ce serait beaucoup moins perturbant de l'avoir comme une sorte de matrice et d'avoir alors size(v.') == (1,length(v)) comme nous le faisons maintenant. La seule différence serait qu'une double transposition vous redonne un vecteur et v'v produirait un scalaire. Si l'on veut traiter une transposition comme un vecteur, on peut puisque length(v') donne la bonne réponse et l'indexation linéaire fonctionne comme prévu.

Je suis d'accord à 110% avec @StefanKarpinski. J'ai joué à en faire un type de vecteur, mais cela n'a pas trop de sens et est plutôt cassant, pour les types de raisons qui ont été discutés plus tôt dans ce fil.

L'approche consistant à lui donner la taille (1, n) signifie qu'en termes de comportement Array , il se comporte exactement comme il le fait actuellement. Les opérations _only_ qui le distingueront d'un 1-by-N Matrix sont un comportement sous ' , .' , * , \ . et / .

Aucun de ceux-ci n'est un opérateur de type «tableau». Ils sont là uniquement pour implémenter l'algèbre linéaire entre matrices et vecteurs, et proviennent directement de MATLAB ("matrice de laboratoire"). Ce raffinement final dit simplement que le size(tvec, 1) = 1 au compilateur _et_ que je veux que v'' soit v . (Je pense que c'est un peu comme un StaticArray où une dimension est fixe et l'autre est dimensionnée dynamiquement).

Si vous voulez simplement v '' == v, alors c'est ok pour typeof (v ') <: AbstractMatrix.

Droite.

Mais si vous voulez les autres choses dont nous avons parlé dans ce fil, comme typeof (v'v) <: Scalar ou typeof (v'A) <: AbstractVector, alors il faut que ce soit une bête différente, plus compliquée.

Pourquoi? Nous ne cassons aucune de ses propriétés de type tableau, donc cela peut toujours être un tableau. Tout appel de point fonctionnera comme avant, et d'autres opérations vectorisées seront supprimées. Je pense que * est "spécialisé" sur la connaissance des différentes formes de Vector et Matrix et que c'est parce que nous essayons d'émuler le comportement de l'algèbre linéaire écrite. Faire de v' * v un scalaire est _très_ des mathématiques standard et c'est mon interprétation que la seule raison pour laquelle cela ne l'a pas été depuis le début est qu'il n'est pas trivial de mettre en œuvre de manière cohérente (et l'inspiration que Julia tire MATLAB), mais Stefan pourrait clarifier cela. Faire du produit interne un scalaire est le seul changement de rupture ici dont on puisse parler (il y a d'autres choses très mineures) - je ne vois pas pourquoi cela seul rendrait inapproprié d'être un Array (il existe de nombreux types sur Array qui n'ont pas de ' valides, .' , * , \ et / définis _at all_ , notablement rang 3+)

L'un des problèmes que j'ai rencontrés l'année dernière était les ambiguïtés; IIRC, ils étaient beaucoup plus simples à résoudre lorsque la transposition vectorielle n'était pas un tableau.

Oui, je suis un peu inquiet de la profondeur que cela deviendra avant d'avoir fini ... :)

En règle générale, ce serait merveilleux si / quand nous rendons Base moins monolithique et le factorisons dans des bibliothèques standard. Avoir un package ou un module autonome qui définit simplement AbstractArray et Array rendrait ce type de développement plus simple.

Quel était exactement le problème avec la proposition de @Jutho ? * ne pouvait-il pas automatiquement (conjuguer) transposer le vecteur à gauche?

Si nous avons * la multiplication matricielle, ' la transposition matricielle (conjuguée) (laisse les vecteurs inchangés), et deux opérateurs promote qui ajoute un singleton final et demote qui supprime un singleton de fin, alors nous pouvons construire une application droite *: (n,m) -> (m,) -> (n,) , une application gauche *: (n,) -> (n,m) -> (m,) , un produit scalaire *: (n,) -> (m,) -> (,) et un produit externe ×: (n,) -> (m,) -> (n,m) tel 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)'

Je ne suis pas sûr d'avoir besoin de transposer un vecteur avec ces opérateurs. Est-ce que je manque quelque chose?

Edit: Pas exactement la proposition de @Jutho .

@Armavica , je ne suis pas sûr que ce soit exactement ma proposition d'attribuer cette signification à * mais plutôt à un nouvel opérateur (unicode) , qui équivaut actuellement à dot . De plus, je ne pense pas que vous seriez en mesure d'implémenter les approches promote et demote manière stable.

Le puriste en moi est d'accord avec @andreasnoack sur le fait que la transposition d'un vecteur lui-même devrait être évitée (je préfère personnellement écrire dot(v,w) sur v'w ou v'*w tout moment), mais je peux aussi apprécier la flexibilité de le faire fonctionner de la manière proposée par @andyferris . Par conséquent, je ne vais rien ajouter de plus à cette discussion et soutenir toute tentative sensée de faire fonctionner une implémentation réelle.

D'accord, @Jutho. Il est intéressant de noter que les deux ne sont pas mutuellement exclusifs: le fait de ne pas avoir de transposition sur le vecteur défini par défaut signifie que cette fonctionnalité peut légitimement vivre dans un package, un module ou une «bibliothèque standard» séparé (c'est-à-dire sans «piratage de type»).

(Personnellement, je préfère écrire point (v, w) plutôt que v'w ou v '* w à tout moment)

Jutho - d'un autre côté, je parie que vous n'écrivez pas | ψ> ⋅ | ψ> sur un morceau de papier ou un tableau blanc, mais plutôt <ψ | ψ> (et la même analogie vaut pour la façon dont nous dessinons les produits internes avec des diagrammes de réseaux tensoriels).

Bien sûr, j'aimerais voir la notation braket de Dirac comme la formulation standard de l'algèbre linéaire dans toutes les mathématiques ou même la science, et par extension dans Julia, mais cela ne se produira probablement pas.

Maintenant, vous me forcez à faire une digression, ce que je n'allais plus faire. Je suis certainement d'accord pour préférer <ψ | ψ> pour le produit interne, mais c'est indépendant du fait que je ne pense pas à <ψ | comme le conjugué hermitien de | ψ>. La conjugaison hermitienne est définie pour les opérateurs. L'isomorphisme naturel entre les vecteurs et leurs vecteurs doubles associés (covecteurs, fonctionnelles linéaires, ...) est connu sous le nom de théorème de représentation de Riesz , bien que le premier soit bien sûr équivalent à la conjugaison hermitienne lors de l'interprétation des vecteurs de longueur n comme des cartes linéaires de C à C ^ n, c'est-à-dire sous forme de matrices de taille (n,1) .

Bien sûr, j'aimerais voir la notation braket de Dirac comme la formulation standard de l'algèbre linéaire dans toutes les mathématiques ou même la science, et par extension dans Julia, mais cela ne se produira probablement pas.

Lol

Maintenant, vous me forcez à faire une digression, ce que je n'allais plus faire.

Désolé ... je n'aurais vraiment pas dû le mentionner ... :)

Je ne pense pas à <ψ | comme le conjugué hermitien de | ψ>. La conjugaison hermitienne est définie pour les opérateurs.

Certes, je n'avais pas pensé à cela.

L'isomorphisme naturel entre les vecteurs et leurs vecteurs duels associés (covecteurs, fonctionnelles linéaires, ...) est connu sous le nom de théorème de représentation de Riesz, bien que le premier soit bien sûr équivalent à la conjugaison hermitienne lors de l'interprétation des vecteurs de longueur n comme des applications linéaires de C à C ^ n, c'est-à-dire sous forme de matrices de taille (n, 1).

J'essaie de ne pas faire une digression (mais je suis mauvais pour ça), mais je pense que nous obtenons un peu de ce dernier morceau puisque nous associons AbstractVector avec un 1D Array . En d'autres termes, AbstractVector ne signifie pas "vecteur abstrait" au sens mathématique, mais signifie plutôt "les éléments numériques d'un vecteur sur une base prédéfinie". MATLAB s'en est sorti facilement car les vecteurs étaient de taille (n,1) .

Comme matière à réflexion, comment appelle-t-on l'opérateur (antilinéaire) qui prend tous les tenseurs de la forme |a>|b><c| et les mappe à |c><a|<b| ? J'ai toujours regroupé cela avec la conjugaison des opérateurs vectoriels dual et standard, comme "conjugaison hermitienne" mais c'est peut-être trop blasé.

J'ai eu une conversation avec @alanedelman qui a cristallisé quelques choses sur ma position sur # 4774:

  • introduire Covector ou RowVector type;
  • pour les opérations algébriques linéaires ( * , ' , .' , / , \ , peut-être norm ? autres? ) ce type agit comme un covecteur en algèbre linéaire;
  • pour les opérations de tableau (tout le reste), ce type agit comme un tableau 1 × n à 2 dimensions;
  • en particulier, size(v') d'un covecteur est (1, length(v)) .

Cette approche nécessite de classer toutes les fonctions génériques de Base comme algébriques linéaires ou non. En particulier, size est une fonction de tableau et n'a pas de sens dans le domaine de l'algèbre linéaire, qui n'a essentiellement que des vecteurs (qui n'ont pas de "forme", juste une cardinalité de dimensions), des transformations linéaires (qui peuvent être représentés par des tableaux à 2 dimensions) et des scalaires. Un exemple particulier qui est apparu était cumsum , qui fonctionne actuellement sur des tableaux à 2 dimensions comme si un argument de dimension de 1 était fourni. Ceci est incompatible avec sum , qui plutôt que de passer par défaut à un argument de dimension de 1 retourne la somme du tableau entier. Il empêche également cumsum d'opérer sur les covecteurs d'une manière correspondante à la façon dont il opère sur les vecteurs. Je pense que cumsum devrait fonctionner sur des tableaux généraux en additionnant de manière cumulative dans un ordre majeur de colonne à N dimensions. Plus généralement, les arguments de dimension ne doivent pas être définis par défaut sur 1.

Je serais légèrement inquiet s'il n'y avait pas d'interface pour identifier les covecteurs. La plupart des opérations, par exemple size et ndims, diraient qu'elles sont exactement comme les autres tableaux 2d ou 1d. Ce n'est qu'en essayant, par exemple, un produit scalaire que vous voyez une différence. AFAICT vous devrez vérifier si l'objet est un RowVector, mais il est très rare d'exiger qu'un objet ait un type spécifique afin d'avoir une certaine propriété générale.

@StefanKarpinski Je suis d'accord avec tout cela. Surtout que les fonctions sont soit des opérations "tableau", soit des opérations "algèbre linéaire".

J'ai commencé à une taille = (1, n) TransposedVector ici, ce qui est exactement comme vous le dites. Il m'a fallu un certain temps pour obtenir une bonne couverture des tests et de toutes les combinaisons * , \ , / pour chaque c et t méthode et les méthodes de mutation, tout en évitant les ambiguïtés avec les autres méthodes de base. C'est un travail lent mais systématique, et à partir de là, je pensais que nous pourrions le tirer vers la base quand il est prêt (peut-être renommer les choses).

Leur est une distinction avec Covector où cela devrait vraiment être la transposition conjuguée (ou une transformation encore plus générale, dans le cas général!), Tandis qu'un RowVector ou TransposedVector est conceptuellement plus simple.

@JeffBezanson Peut-on faire quelque chose avec indices() pour avoir une dimension "singleton"?

mais il est très rare d'exiger qu'un objet ait un type spécifique pour avoir une certaine propriété générale.

Bon, ce serait cool si c'était un trait ou quelque chose comme ça. J'espérais que nous pourrions démêler l'algèbre linéaire des tableaux plus généralement, mais c'est un énorme changement. et ne pourrait probablement pas être parfait sans une belle syntaxe de trait implémentée dans Julia. Je pense que le problème ici est que nous mappons 3 choses (vecteurs, covecteurs et matrices) sur deux types ( AbstractArray{1} et AbstractArray{2} ), et donc l'un d'eux (covecteurs) devient un sous-type spécial de l'autre.

J'aurais mis AbstractTransposedVector dans le paquet si j'avais pu penser à un endroit où quelqu'un aurait besoin de quelque chose d'autre que l'implémentation de base "wrapper".

@JeffBezanson : Je ne comprends pas votre inquiétude. L'idée est qu'il se comporte exactement comme un tableau abstrait 1 × n 2d, sauf pour les opérations d'algèbre linéaire, pour lesquelles il se comporte comme le double espace des vecteurs colonnes (qui est isomorphe aux tableaux abstraits 1 × n 2d). Si vous voulez vérifier un covector, vous pouvez vérifier le type par exemple en envoyant dessus.

Une mise à jour sur mes tentatives pour résoudre ce problème:

TransposedVectors.jl est maintenant, je crois, "fonctionnalité complète". Il contient toutes les machines nécessaires pour faire ce dont @StefanKarpinski a parlé ici - la transposition d'un vecteur est un wrapper d'un vecteur qui se comporte comme un tableau abstrait à 2 dimensions de taille 1xn - mais pour les opérations d'algèbre linéaire ( * , / , \ , ' , .' et norm ) il se comporte comme un vecteur ligne (ou double vecteur).

Vous pouvez le vérifier comme ceci:

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

Il peut y avoir des dégradations de performances - une analyse comparative serait utile. Dans certains cas, il fera moins de copies (la transposition est paresseuse) et dans d'autres cas il fera plus de copies (parfois une copie conjuguée d'un vecteur (jamais une matrice) est créée dans Ac_mul_Bc par exemple). Et le wrapper lui-même a un léger coût jusqu'à ce que nous intégrions des champs mutables dans les immuables (heureusement pour moi, cela fonctionne déjà bien avec StaticArrays.jl : smile :). Il y a aussi le problème de s'assurer qu'il s'agit d'une sorte de StridedArray cas échéant.

Si les gens aiment cette approche, nous pouvons voir comment faire bientôt un PR pour migrer le code vers Base (le paquet a déjà des tests unitaires et toutes les ambiguïtés sont résolues). (aussi, @jiahao avait mentionné vouloir expérimenter une version de tableau abstrait à 1 dimension d'un vecteur double, pas sûr si des progrès ont été réalisés?)

Les gens pensent-ils qu'un tel PR aurait un sens dans la v0.6 sans un type de wrapper pour les matrices transposées? Alors que les vecteurs transposés sont un changement sémantique, les matrices transposées sont plus une optimisation, donc je suppose qu'elles pourraient entrer séparément. Bien sûr, cela peut sembler "étrange" si certaines transpositions sont des vues et certaines sont des copies - des opinions? Un autre point à considérer est qu'une fois que les matrices et les vecteurs transposés sont entrés, beaucoup de travail devrait être fait pour supprimer toutes les fonctions At_mul_Bt , remplacées par des méthodes sur * , pour compléter le transition (même si la simplification sera gratifiante!) - Je doute que quiconque puisse ou veuille faire tout cela d'ici la fin de ce mois ...

Excellent travail, @andyferris! Avez-vous expérimenté un wrapper générique Conjugate ?

@andyferris J'ai donné un coup de pied aux pneus, et j'aime ça un peu. Semble strictement une amélioration, et j'espère que les problèmes de performance pourront être traités au fur et à mesure que nous les trouvons. Voyons ce que les autres ont à dire.

Merci les gars :)

Avez-vous expérimenté un wrapper générique Conjugate ?

Pas encore, même si cela pourrait être possible sans trop d'efforts, mais pour être honnête, je craignais de ne pas finir ça fin décembre. En regardant vers l'avenir, je pensais qu'à la fin nous pourrions avoir les types "unionall" suivants à envoyer pour l'algèbre linéaire:

  • AbstractVector
  • Conjugate{V where V <: AbstractVector} (ou Conj{V} , peut-être même ConjArray ? Bien sûr, il accepterait des tableaux de n'importe quelle dimension)
  • TransposedVector (ou RowVector ?)
  • TransposedVector{Conjugate{V where V <: AbstractVector}}
  • AbstractMatrix
  • Conjugate{M where M <: AbstractMatrix}
  • TransposedMatrix (ou simplement Transpose{M} ?)
  • TransposedMatrix{Conjugate{M where M<:AbstractMatrix}}

(plus tous les traits donnés du stockage sous-jacent, tels que ce que nous appelons maintenant DenseArray et StridedArray - j'espère que # 18457 facilitera légèrement leur expression).

Mis à part la dénomination, cela vous semble-t-il un plan raisonnable?

En ce qui concerne le bikeshedding immédiat, pour un PR à la base de ce qui est actuellement dans le package, préférons-nous TransposedVector , RowVector , un wrapper générique Transpose pour les vecteurs et les matrices , ou autre chose?

Je pense que le comportement fourni par l' implémentation de

Le problème est de définir de nouveaux types de tableaux. Par exemple DArray est un sous-type de AbstractArray , donc un DArray peut aussi être un AbstractVector ou un AbstractMatrix . Idéalement, nous étendrions simplement la distinction Vector / Matrix à Row / Column / Matrix, donc un DArray pourrait être un AbstractRow , AbstractCol ou AbstractMatrix . La hiérarchie des types serait quelque chose comme

AbstractArray
    AbstractVector
        AbstractRowVector
        AbstractColumnVector
    AbstractMatrix
    ...

J'en parlais avec

Il peut y avoir une priorité ici, en ce sens que tous les AbstractVector ne peuvent pas être utilisés comme colonnes, par exemple:

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)

qui pourrait résoudre les problèmes que j'avais de retour sur https://github.com/JuliaLang/julia/issues/4774#issuecomment -59428215

C'est juste des parenthèses manquantes autour de la plage?

C'est juste un problème de priorité:

julia> randn(10,10) * (1.0:10.0)
10-element Array{Float64,1}:
 -22.4311
  ⋮

Je suis d'accord que la solution optimale ici semble exiger des traits, mais je ne pense pas que cela devrait retarder le travail de

ah, bon point. J'adorerais avoir une priorité incorrecte des espaces blancs serait une erreur de syntaxe, similaire à Fortress.

Le groupe MIT a souligné qu'une façon d'implémenter la hiérarchie de types que j'ai décrite ci-dessus consiste à ajouter un paramètre de type à la hiérarchie du tableau:

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}

Je n'ai pas élaboré toutes les implications de cela - probablement le pire est que Array{Int,1} devient un type abstrait - mais cela semble obtenir la hiérarchie des types et les abstractions nécessaires à peu près exactement.

Pour ce que ça vaut, c'est exactement un cas d'utilisation pour les supertypes incomplètement paramétrés; ils peuvent devenir des paramètres implicites par défaut.

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

Bien sûr, cela rend l'envoi un peu compliqué… et cela ne vaut peut-être pas la peine d'être soutenu. C'est juste un spit-ball.

Cela me semble équivalent à définir

type Array{T,N} <: AbstractArray{T,N,false}
end

Ce que nous pourrions vouloir ici, c'est une sorte de "paramètres de type par défaut", de sorte que le fait de créer le type Array{X,Y} où X et Y n'ont pas de variables libres donne en fait Array{X,Y,false} .

Une autre idée: conserver la notation Householder pour les produits internes v'v et multiplier le covecteur gauche v'A en faisant de l'infixe ' son propre opérateur au lieu de les analyser comme v'*v et v'*A . Couplé à la création de v' l'identité, v*v une erreur et v*A une erreur, cela pourrait donner une grande partie de ce qui est souhaité. Vous devrez écrire les produits externes sous la forme outer(v,v) .

Dans un tel schéma, v' serait un noop ou même une erreur - puisqu'il n'y a aucune raison de transposer un vecteur dans un tel schéma, cela n'aurait de sens que pour une matrice.

@JeffBezanson Je suis d'accord qu'avoir un AbstractRowVector serait mieux (mais honnêtement, je ne peux pas penser à un cas d'utilisation, donc je ne l'ai pas implémenté dans le package). Je voudrais également souligner que cela pourrait vivre comme un sous-type de AbstractMatrix . La raison pour laquelle j'ai évolué dans cette direction est que broadcast semble être plus un concept fondamental pour Julia que l'algèbre linéaire ne l'est, et les gens s'attendront à ce qu'un vecteur ligne soit, eh bien, une ligne!

Bien sûr, avoir RowVector <: AbstractMatrix est une utilisation malheureuse de la terminologie! Je pense que cela résulte du fait que des tableaux 2D et des matrices abstraites portent le même nom.

Je l'ai déjà dit, bien plus haut, mais comme ce problème est si long, je le répète: dans un langage générique comme Julia, la propriété "array of data" doit être la principale considération pour AbstractArray . Le fait de savoir comment vous voulez que broadcast se comporte pour un vecteur "transposé" vous indique s'il s'agit d'un vecteur 1D ou 2D. Si vous voulez penser en termes de lignes et de colonnes, alors les vecteurs de lignes 1 x n plus de sens. Si vous voulez considérer des vecteurs doubles, alors 1D a plus de sens - et j'en suis heureux aussi! Mais prendre le dual d'un vecteur est plus compliqué que de remodeler les données (par exemple, nous devons au moins prendre en charge la conjugaison).

Je suppose que l'image de la "ligne" correspondrait aux attentes des programmeurs "typiques", tandis que les personnes ayant une formation avancée en mathématiques pourraient peut-être mieux utiliser les vecteurs doubles car il s'agit d'une meilleure abstraction (même si je suis sûr qu'ils seraient en mesure de sympathiser avec ceux qui n'ont que des connaissances de base en algèbre linéaire). Alors - quel public cible Julia - des personnes avec plus de finesse mathématique que de nombreux utilisateurs typiques de MATLAB, ou des personnes avec moins?

(Mon opinion était que comme Julia visait à être un langage de programmation «générique», ce dernier public était la cible).

Puisque nous discutons des arborescences de types possibles - à l'avenir, avec Buffer et des "listes" redimensionnables, j'imagine un arbre abstrait d'interfaces qui va dans le sens de

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

Bien sûr, nous pourrions également avoir AbstractDualVector{T} <: AbstractArray{T,1} au lieu de AbstractRowVector .

Avoir un type Array flexible et concret pour s'adapter à tous ces éléments serait difficile (et peut-être inutile). Les traits nous permettraient certainement d'exprimer plus facilement ces différences dans les interfaces prises en charge.

Avoir AbstractVector étant à la fois un C ++ std::vector et un vecteur d'algèbre linéaire m'a semblé un peu effronté :) (d'autant plus que l'interface de tableau signifie qu'il ne peut jamais être vraiment un "vecteur abstrait" dans le sens de l'algèbre linéaire (par exemple sans base))

Oui, il serait bon de séparer le comportement de redimensionnement. Je suis à bord pour ça.

Cette hiérarchie de types semble impliquer que nous aurions des types concrets séparés "matrice" et "tableau 2-d" dans Base. Nous pourrions essayer de le contourner, par exemple en demandant au constructeur de Array{T,2} renvoyer un autre type de matrice, mais cela semble assez moche. Peut-être que je ne comprends pas bien.

Cette hiérarchie de types semble impliquer que nous aurions des types concrets séparés "matrice" et "tableau 2-d" dans Base.

Bon ... Je pense que pour bien faire ça, nous aurions besoin de traits. Gardez à l'esprit que je l'ai appelé un "arbre abstrait d'interfaces". Tenter cela pour le moment nécessiterait quelque chose de laid comme vous l'avez dit. Mais nous pourrions probablement insérer AbstractList <: AbstractVector (et déplacer les méthodes associées) dès que Buffer est terminé.

Pour le moment, la connexion entre les interfaces prises en charge et l'arborescence de types est assez lâche. Il est difficile de savoir à la répartition si un tableau (abstrait) est mutable (c'est-à-dire prend en charge setindex! ), s'il est redimensionnable, strided ne fonctionne que pour les types définis dans Base , etc. difficile pour les utilisateurs de fournir une fonction avec des méthodes qui fonctionnent et sont efficaces avec un large éventail d'entrées (c'est mon expérience de StaticArrays ).

Une pensée pour la conversation sur les régions mathématiques de la hiérarchie des types et les rôles raisonnables pour les abstractions paramétrées. Lorsque cela est possible, il est bon pour Julia de simplifier et d'alléger toute séparation entre la façon dont le code est écrit pour faire ce que l'expertise a l'intention de faire de l'intention exprimée par des experts.

Une convention que nous pourrions choisir d'utiliser rend la pratique courante en utilisant un type abstrait autre que Any pour façonner une région ontotopologique au milieu de laquelle les types concrets concomitants trouvent le repos partagé facile. Cela apporterait, à très peu de frais, une plus grande sensibilité multi-contextuelle - la compréhension d'une partie de la hiérarchie des types aide une approche à d'autres parties.

Comme je vois cela, c'est un apporteur de rapport généralisé. Une abstraction élucidative illumine comme un voisinage les concrétions, les alikenesses génératives qui construisent. Avec des paramétrisations porteuses de la clarté simple de l'intuition, Julia accomplit beaucoup plus avec moins de temps insensé.

OK, tout autre commentaire sur TransposedVectors serait apprécié. Je pense que cela devient assez solide et prêt à être transformé en PR, mais il y a quelques problèmes pertinents que j'ai énumérés ici .

(par exemple: RowVector -il meilleur que TransposedVector ? Est-ce que [1 2 3] a RowVector ou a Matrix ? Qu'est-ce que indices(row, 1) ?)

+1 pour RowVector

Le 20 décembre 2016 à 07h01, "Andy Ferris" [email protected] a écrit:

OK, tout autre commentaire sur TransposedVectors serait apprécié. Je ressens
ça devient assez solide et prêt à être transformé en PR, mais il y a
quelques problèmes pertinents que j'ai énumérés ici
https://github.com/andyferris/TransposedVectors.jl/issues .

(Par exemple: RowVector est-il un meilleur nom que TransposedVector? Est-ce que [1 2
3] un RowVector ou une Matrix? Qu'est-ce que les indices (ligne, 1)?)

-
Vous recevez ceci parce que vous avez commenté.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment-268170323 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAm20YYqsXmprI23GgI5PYyWStpTOq5qks5rJ309gaJpZM4BMOXs
.

J'aimerais vraiment garder la convention ligne / colonne- en dehors de cela, et je pense que cela semble étrange d'avoir Vector et RowVector (ou même ColVector et RowVector ). +1 pour TransposedVector ou DualVector

@felixrehren Je pense que DualVector devrait avoir une sémantique différente de ce que j'ai implémenté, étant principalement unidimensionnel (mathématiquement, le dual d'un vecteur est un vecteur) et avoir d'autres propriétés de dualité (par exemple, conjugaison complexe) . Ce qui est bien, mais je l'ai trouvé un peu plus difficile à mettre en œuvre et un peu moins rétrocompatible.

Le nom TransposedVector est correct. Mais c'est un peu long. De plus, cela suggère en quelque sorte qu'un objet de ce type ne peut être obtenu qu'en transposant un Vector . Mais je suppose que vous pourriez aussi créer un TransposedVector par d'autres moyens, par exemple en extrayant une ligne d'une matrice?

Je pense que RowVector serait un bon nom - il est concis, précis et intuitif. @felixrehren , je pense que l'image des lignes / colonnes est utile. C'est probablement même inévitable, car chaque fois que vous effectuez une concaténation ou d'autres opérations de tableau courantes (autres que la multiplication), vous devez réfléchir à l'orientation du vecteur.

DualVector n'est pas mal non plus, mais CoVector semblerait moins formel.

Le PR que j'ai menacé plus tôt est maintenant soumis au # 19670. Je suis allé avec RowVector pour le moment.

Mais je suppose que vous pouvez également créer un vecteur transposé par d'autres moyens, par exemple en extrayant une ligne d'une matrice?

C'est en fait un point de friction - je n'ai pas encore pensé à une bonne syntaxe pour cela. Des idées?

Mais je suppose que vous pouvez également créer un vecteur transposé par d'autres moyens, par exemple en extrayant une ligne d'une matrice?

C'est en fait un point de friction - je n'ai pas encore pensé à une bonne syntaxe pour cela. Des idées?

Bien qu'au début, il semble intéressant que matrix[scalar,range] (et d'autres constructions similaires) donnent un vecteur de ligne, ce serait un écart significatif par rapport à la sémantique d'indexation actuelle pour les tableaux multidimensionnels, et des cas particuliers me rendent méfiant.

Au lieu de cela, j'aimerais voir RowVector (et Vector d'ailleurs) convertir n'importe quel type itérable dans le type de vecteur approprié. Ensuite, vous pouvez faire quelque chose comme RowVector(matrix[scalar,range]) qui serait assez clair et ne perturberait pas le comportement actuel de l'indexation des tableaux.

À droite, la ligne i ème peut être extraite en forme de ligne par A[i,:].' , actuellement dans la v0.5 et continuerait de le faire à l'avenir (pour faire un RowVector ou Transpose{V<:AbstractVector} ou tout ce sur quoi nous nous déciderons éventuellement (voir ici pour une discussion en cours)). C'est peut-être la réponse.

Qu'en est-il simplement d'ajouter deux nouvelles fonctions à Base?
row(A,i) et col(A,i)
Ce dernier n'est pas nécessaire, mais juste pour la symétrie. C'est concis, clair et autant de caractères que A[i,:].'

@benninkrs qui a du sens. En revanche, mon interprétation intuitive est celle de l'algèbre linéaire, où un vecteur n'a aucune orientation du tout. Tous les points de vue à ce sujet sont raisonnables, je n'aime tout simplement pas Vector et RowVector ensemble parce que le nom semble mélanger un paradigme abstrait et concret.

À ce stade, je pense que nous devons soit opter pour quelque chose basé sur l' implémentation de v' tel qu'il est actuellement - c'est-à-dire produisant une matrice de lignes - mais parsez a'b comme un opérateur infixe avec la priorité juste en dessous multiplication. En d'autres termes, nous aurions les comportements suivants:

  1. v' est une matrice de lignes (ctranspose)
  2. v'v est un scalaire (produit
  3. v'*v est un vecteur à 1 élément (matrice de lignes * vecteur)
  4. v*v' est une matrice de produit externe (
  5. v'A est une matrice de lignes ("vecmat")
  6. v'*A est une matrice de lignes (matmat)
  7. v'A*v est un scalaire (matvec A * v puis produit scalaire)
  8. (v'A)*v est un vecteur à 1 élément (vecmat puis matvec)
  9. v'*A*v est un vecteur à 1 élément (matmat puis matvec)
  10. v'' est une matrice de colonnes (vecteur ctranspose, puis matrice ctranspose)

Dans la configuration actuelle, 2 et 3 sont équivalents et 7, 8 et 9 sont équivalents, ce que ce changement rompt. Mais surtout, les éléments audacieux sont ceux que les gens recherchent généralement, car ils sont les plus courts et les plus pratiques des formes similaires - et ils font tous ce que nous aimerions qu'ils fassent. Pas de nouveaux types, juste un nouvel opérateur d'infixe. Le principal inconvénient est 10 - v'' est toujours une matrice de colonnes. On peut soutenir que cela pourrait être considéré comme un avantage puisque le suffixe '' est l'opérateur pour transformer un vecteur en matrice de colonnes.

En prenant un peu de recul, je pense que ce que nous avons appris, c'est que sans fonctionnalité supplémentaire d'étiquetage de haut en bas ou de dimension ou sans traiter ≤ 2 dimensions comme fongibles comme le fait Matlab, les tableaux multidimensionnels ne peuvent pas vraiment être mis en cohérence avec l'algèbre linéaire. Il ne nous reste donc qu'une question de commodité - pouvons-nous laisser les gens exprimer des opérations d'algèbre linéaire courantes sur des vecteurs et des matrices de manière pratique sans trop compliquer les tableaux? Je ne suis pas complètement fixé sur cette approche, mais je pense que nous devrions sérieusement envisager cette approche et d'autres qui abordent la commodité syntaxique sans trop gâcher notre hiérarchie de types de tableaux.

Une autre approche serait d'analyser spécialement l'infixe a'b (juste en dessous de * ) et d'avoir v' renvoyer un vecteur conjugué. Plus généralement, postfix A' pourrait conjuguer un tableau et inverser paresseusement ses dimensions tandis que A.' inverserait simplement paresseusement les dimensions d'un tableau agissant ainsi comme l'identité sur les vecteurs. Dans ce schéma, la liste des propriétés pourrait être la suivante:

  1. v' est un vecteur (conjugué)
  2. v'v est un scalaire (produit
  3. v'*v est une erreur (recommandez v'v pour le produit intérieur et outer(v,v) pour le produit extérieur) †
  4. v*v' est une erreur (recommandez v'v pour le produit intérieur et outer(v,v) pour le produit extérieur) †
  5. v'A est un vecteur (vecmat)
  6. v'*A est une erreur (recommander v'A pour vecmat)
  7. v'A*v est un scalaire (matvec A * v puis produit scalaire)
  8. (v'A)*v est une erreur (recommandez v'v pour le produit intérieur et outer(v,v) pour le produit extérieur) †
  9. v'A'v est un scalaire ( v'(A'v) - conjuguer matvec puis produit interne)
  10. v'' est un vecteur ( v'' === v et v.' === v )

Maintenant que j'écris toutes ces propriétés, c'est peut-être l'option préférable: tous les cas d'erreur servent en fait à aider les gens à découvrir et à utiliser les syntaxes préférées, et bien sûr, il a la propriété v'' === v souhaitable (et s'accorde bien avec .' étant un opérateur d'inversion de dimension générique). Avoir des syntaxes très similaires qui ne sont que légèrement différentes est probablement plus déroutant.

† Nous pourrions potentiellement les attraper au moment de l'analyse pour des erreurs plus précises au risque de donner des erreurs pour les cas où le code utilisateur a surchargé ' et * pour que ces opérations fonctionnent. Je pense qu'il peut être nécessaire d'avoir un wrapper de conjugué paresseux pour corriger ces recommandations, mais nous en avons besoin de toute façon pour # 5332.

En prenant un peu de recul, je pense que ce que nous avons appris, c'est que sans fonctionnalité supplémentaire d'étiquetage de haut en bas ou de dimension ou sans traiter ≤ 2 dimensions comme fongibles comme le fait Matlab, les tableaux multidimensionnels ne peuvent pas vraiment être mis en cohérence avec l'algèbre linéaire. Il ne nous reste donc qu'une question de commodité - pouvons-nous laisser les gens exprimer des opérations d'algèbre linéaire courantes sur des vecteurs et des matrices de manière pratique sans trop compliquer les tableaux? Je ne suis pas complètement fixé sur cette approche, mais je pense que nous devrions sérieusement envisager cette approche et d'autres qui abordent la commodité syntaxique sans trop gâcher notre hiérarchie de types de tableaux.

: 100:

Faire explicitement des opérations de tableau générique postfix ' et .' (plutôt que d'algèbre linéaire) évite bien le doublement des types de stockage et d'algèbre linéaire, et laisse la porte ouverte à des frameworks impliquant moins de compromis. Dans l'intervalle, la capacité de ces opérations à simuler la notation de Householder dans la plupart des cas courants devrait fournir la plupart de la commodité souhaitée. Aussi moins de code et de complexité. Meilleur!

Un problème avec v.' étant non-op est que A .+ v.' changerait la signification de l'ajout des valeurs de v à chaque colonne de A à l'ajout des valeurs aux lignes de A . Cela rendrait généralement plus difficile d'effectuer des opérations de type ligne avec des vecteurs et il faudrait certainement un cycle de dépréciation complet pour éviter de faire en silence le code faire la mauvaise chose (dans des cas comme celui-ci où A se trouve être carré).

À ce stade, je pense que nous devons soit opter pour quelque chose basé sur l' implémentation de

Je me rends compte que la date limite pour la v0.6 est presque arrivée, mais je mets en garde de ne pas jeter le bébé avec l'eau du bain. À ce stade, il semble que les vues RowVector plus mentionnées pour la transposition de matrice et la conjugaison de tableaux nous permettront d'obtenir:

  • IMO, types d'algèbre linéaire un peu plus raisonnables (nous ne nions pas l'existence de vecteurs doubles comme maintenant, bien qu'un vecteur ligne puisse être considéré comme un cas particulier de vecteur double)
  • v'' === v
  • Certaines des choses sur la liste de Stefan comme v1'v2 sont un produit scalaire
  • Sémantique de tableau presque rétrocompatible - par exemple, le résultat de size(v') est inchangé, mais nous avons v'' comme unidimensionnel
  • Les wrappers Lazy conj et transpose peuvent augmenter les performances dans certaines circonstances et être utiles de toute façon
  • Suppression de toutes les fonctions Ac_mul_Bc en faveur de * et A_mul_B! uniquement (et de même pour \ , / ).

Faire fonctionner tout cela pour Array honnêtement ne demanderait pas trop d'efforts (pour moi, le problème est de trouver du temps à cette période de l'année et de traquer tous les autres types que nous avons dans notre suite d'algèbre linéaire). Et le dernier point serait un soupir de soulagement.

D'un autre côté - à mon humble avis, ces règles semblent compliquer légèrement l'analyse et peuvent être un peu déroutantes et / ou surprenantes comment elles composent ' avec * (3, 4, 6 et 8 fonctionneraient avec RowVector ).

Et oui, nous devrions déprécier v.' ou quelque chose pour mettre en évidence les bogues potentiels, auquel cas il semble presque préférable de faire de v.' une erreur de méthode manquante (nous ne prendrons tout simplement pas en charge row / dual vectors, mais n'empêchera pas un paquet de le faire s'il le souhaite)

19670 semble prêt ou proche de lui, s'il y a un désir de se faufiler dans la v0.6.

BAM

Woot.

Était-ce notre plus long fil de discussion?

Non, # 11004 en a plus

Désolé. Vous avez raison, j'aurais dû spécifier un fil de discussion

Cette page vous a été utile?
0 / 5 - 0 notes