Julia: Vektortransponierungen ernst nehmen

Erstellt am 10. Nov. 2013  ·  417Kommentare  ·  Quelle: JuliaLang/julia

von @alanedelman :

Wir sollten wirklich sorgfältig darüber nachdenken, wie die Transponierung eines Vektors die verschiedenen A_*op*_B* -Methoden auslösen soll. Es muss möglich sein, neue Typen und hässliche Mathematik zu vermeiden. Zum Beispiel sind Vektor-Havektor, der einen Vektor liefert (# 2472, # 2936), Vektor ', der eine Matrix ergibt, und Vektor', der eine Matrix ergibt (# 2686), alles schlechte Mathematik.

Was für mich mathematisch funktioniert (was die Einführung eines neuen Typs vermeidet), ist das für ein eindimensionales Vector v :

  • v' ist ein No-Op (dh gibt nur v ),
  • v'v oder v'*v ist ein Skalar,
  • v*v' ist eine Matrix und
  • v'A oder v'*A (wobei A ein AbstractMatrix ) ist ein Vektor

Eine allgemeine _N_-dimensionale Transponierung kehrt die Reihenfolge der Indizes um. Ein Vektor mit einem Index sollte bei der Transposition invariant sein.

In der Praxis wird v' selten isoliert verwendet und tritt normalerweise in Matrixvektorprodukten und Matrixmatrixprodukten auf. Ein häufiges Beispiel wäre die Konstruktion bilinearer Formen v'A*w und quadratischer Formen v'A*v die in konjugierten Gradienten, Rayleigh-Quotienten usw. verwendet werden.

Der einzige Grund für die Einführung eines neuen Transpose{Vector} -Typs wäre die Darstellung des Unterschieds zwischen kontravarianten und kovarianten Vektoren, und ich finde das nicht überzeugend genug.

arrays breaking design linear algebra

Hilfreichster Kommentar

BAM

Alle 417 Kommentare

Zum Beispiel sind Vektor-Havektor, der einen Vektor liefert (# 2472, # 2936), Vektor ', der eine Matrix ergibt, und Vektor', der eine Matrix ergibt (# 2686), alles schlechte Mathematik.

Das Doppel-Dual eines endlichen dimensionalen Vektorraums ist isomorph zu ihm, nicht identisch. Ich bin mir also nicht sicher, wie schlecht Mathematik ist. Es ist mehr so, dass wir die Unterscheidung zwischen Dingen, die in der Mathematik isomorph sind, eher beschönigen, weil das menschliche Gehirn gut darin ist, mit dieser Art von rutschiger Mehrdeutigkeit umzugehen und einfach das Richtige zu tun. Trotzdem stimme ich zu, dass dies verbessert werden sollte, aber nicht, weil es mathematisch falsch ist, sondern weil es ärgerlich ist.

Wie kann v' == v , aber v'*v != v*v ? Ist es sinnvoller, als wir dachten, dass x' * y sein eigener Operator ist?

Das Doppel-Dual eines endlichen dimensionalen Vektorraums ist isomorph zu ihm, nicht identisch.

(Ich spreche jetzt wie ich) Es ist nicht nur isomorph, es ist natürlich isomorph, dh der Isomorphismus ist unabhängig von der Wahl der Basis. Ich kann mir keine praktische Anwendung vorstellen, für die es sich lohnt, zwischen dieser Art von Isomorphismus und einer Identität zu unterscheiden. IMO der Ärgerfaktor kommt von dieser Art der Unterscheidung.

Ist es sinnvoller, als wir dachten, dass x' * y ein eigener Operator ist?

Das war der Eindruck, den ich aus der Diskussion mit @alanedelman am heutigen Nachmittag hatte.

Ich denke, was Jeff verlangt, stimmt mit dem Geld überein ... es sieht so aus, als ob x'_y und x_y 'mehr machen
Sinn als je zuvor.

Ich bin mit @stefan einverstanden. Schlechte Mathematik sollte nicht bedeuten, falsche Mathematik war es
bedeutete ärgerliche Mathematik. Es gibt viele Dinge, die technisch richtig sind, aber nicht sehr schön ...

Wenn wir uns dieser Logik anschließen, haben wir zwei Möglichkeiten

x_x bleibt ein Fehler ..... vielleicht mit einem Vorschlag "Vielleicht möchten Sie Punkt verwenden"
oder x_x ist das Punktprodukt (ich liebe diese Wahl nicht)

Wenn x und x' dasselbe sind, dann, wenn Sie möchten, dass (x')*y dot(x,y) , bedeutet dies, dass x*y auch dot(x,y) . Daraus gibt es keinen Ausweg. Wir könnten x'y und x'*y einer anderen Operation machen, aber ich bin mir nicht sicher, ob das eine großartige Idee ist. Die Leute wollen in der Lage sein, dies auf offensichtliche Weise in Klammern zu setzen und es immer noch funktionieren zu lassen.

Ich möchte darauf hinweisen, dass es im Grunde kein Zurück gibt, wenn wir zulassen, dass x*x das Punktprodukt bedeutet. Das wird überall in den Code der Leute aufgenommen und es wird ein Albtraum sein, ihn auszurotten. Ob natürlicher Isomorphismus oder nicht, das ist keine reine Mathematik, und wir müssen uns damit auseinandersetzen, dass verschiedene Dinge in einem Computer unterschiedlich sind.

Hier ist eine praktische Diskussion über die Unterscheidung von "Up Tuples" und "Down Tuples", die ich mag:

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

Es vermeidet sorgfältig Wörter wie "Vektor" und "Dual", vielleicht um nervige Menschen zu vermeiden. Ich finde die Anwendung auf partielle Derivate jedoch zwingend:

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

Ein weiterer Grund für die Unterscheidung zwischen M[1,:] und M[:,1] ist, dass unser Sendeverhalten derzeit dieses sehr bequeme Verhalten zulässt: M./sum(M,1) ist spaltenstochastisch und M./sum(M,2) ist zeilenstochastisch . Dasselbe könnte für die Normalisierung getan werden, wenn wir die norm -Funktion "repariert" hätten, um die Anwendung über Zeilen und Spalten einfach zu ermöglichen. Natürlich könnten wir immer noch sum(M,1) und sum(M,2) Rückgabematrizen anstelle von Auf- und Ab-Vektoren haben, aber das scheint ein bisschen anders zu sein.

Ich mag die Idee von Auf- und Ab-Vektoren. Das Problem besteht darin, es auf eine Weise auf höhere Dimensionen zu verallgemeinern, die nicht völlig verrückt ist. Oder Sie können Vektoren einfach zu einem Sonderfall machen. Aber das fühlt sich auch falsch an.

Es ist wahr, dass Auf / Ab eine separate Theorie sein kann. Der Ansatz, sie zu verallgemeinern, scheint eine verschachtelte Struktur zu sein, die die Dinge in eine andere Richtung lenkt. Sehr wahrscheinlich gibt es einen Grund, warum sie sie nicht Vektoren nennen.

Außerdem würde x*y = dot(x,y) * nicht assoziativ machen, wie in x*(y*z) vs. (x*y)*z . Ich hoffe wirklich, dass wir das vermeiden können.

Ja. Für mich ist das völlig inakzeptabel. Ich meine, technisch gesehen ist Gleitkomma * nicht assoziativ, aber es ist fast assoziativ, während dies nur offensichtlich nicht assoziativ wäre.

Wir sind uns alle einig, dass x*x nicht das Punktprodukt sein sollte.

Es bleibt die Frage, ob wir uns v'w und v'*w als das Punktprodukt vorstellen können -
Ich mag es wirklich sehr, wenn das so funktioniert.

@ JeffBezanson und ich

Ein Vorschlag ist der folgende:

v' ist ein Fehler für Vektoren (Dies ist, was Mathematica tut)
v'w und v'*w ist ein Punktprodukt (Ergebnis = Skalar)
v*w ist eine äußere Produktmatrix (Ergebnis = Matrix)

Es gibt keinen Unterschied zwischen Zeilen- und Spaltenvektoren. Das hat mir trotzdem gefallen
und war froh, den Präzedenzfall von Mathematica zu sehen
Von mathematica: http://reference.wolfram.com/mathematica/tutorial/VectorsAndMatrices.html
Aufgrund der Art und Weise, wie Mathematica Listen zur Darstellung von Vektoren und Matrizen verwendet, müssen Sie nie zwischen "Zeilen" - und "Spalten" -Vektoren unterscheiden

Benutzer müssen sich bewusst sein, dass es keine Zeilenvektoren gibt .... Punkt.

Wenn also M eine Matrix ist

M[1,:]*v ist ein Fehler ..... (vorausgesetzt, wir gehen mit M[1,:] ist ein Skalar
Die Warnung könnte darauf hindeuten, dot oder '* oder M[i:i,:] versuchen

M[[1],:]*v oder M[1:1,:]*v ist ein Vektor der Länge 1 (Dies ist ohnehin Julias aktuelles Verhalten)

In Bezug auf das eng verwandte Problem in https://groups.google.com/forum/#!topic/julia -users / L3vPeZ7kews

Mathematica komprimiert skalarartige Array-Abschnitte:

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}

[Bearbeiten: Code-Formatierung - @StefanKarpinski]

@ Alanedelman

Angenommen, wir gehen mit M [1,:] ist ein Skalar

meinst du M [1 ,:] ist nur ein Vektor?

Ja Entschuldigung. Meiner Meinung nach verarbeitete M [1,:] den Skalar 1 :-)

Mathematica verwendet den Punkt . anstelle des Sterns *
und geht dann die ganzen 9 Yards und macht (Vektor. Vektor) zu einem Skalar, genau das, was wir vermeiden
mit dem Sternchen.

Es gibt zweifellos viele Probleme mit der Periode, von denen eines darin besteht, dass dies einfach nicht der Fall ist
sehen aus wie der "Punkt" in einem Punktprodukt, und ein anderer davon ist, dass es mit kollidiert
das "pointwise op" Lesen des Punktes,

Unicode bietet sehr gut ein Zeichen mit dem Namen "der Punktoperator".
(char(8901)) die wir uns vorstellen können anzubieten

Wir könnten also (v ⋅ w) zum Synonym für (v'*w)

Zusammenfassend ist ein aktueller Vorschlag Gegenstand der Debatte

  1. Die skalare Indizierung beendet die Dimension auf diese Weise
    A[i,:] ist ein Vektor, ebenso wie A[:,i,j]
  2. Die Vektorindizierung ist dick
    A[ i:i , : ] oder A[ [i], : ] gibt eine Matrix mit einer Zeile zurück
  3. v'w oder v'*w ist das Punktprodukt für Vektoren (ähnlich v*w' für das äußere Produkt)
  4. v' ist für Vektoren undefiniert (zeigen Sie den Benutzer auf permutedims(v,1) ????)
  5. v*A gibt einen Vektor zurück, wenn A eine Matrix ist
  6. v⋅w auch das Punktprodukt zurück (geht aber nicht so weit wie Mathematics . indem an Matrizen gearbeitet wird
  7. v*w ist für Vektoren nicht definiert, aber eine Warnung kann den Benutzer mit guten Vorschlägen, einschließlich

Konsequenzen sind das

ein. Wenn Sie sich daran halten, dass alle Vektoren Spaltenvektoren sind, funktioniert alles
b. Wenn Sie alles zu einer Matrix machen, funktioniert sicherlich alles, und es ist einfach, alles zu einer Matrix zu machen
c. Wenn Ihr Verstand einen Zeilenvektor nicht von einer Einzeilenmatrix unterscheiden kann, sind Sie wahrscheinlich gebildet
höflich und anmutig
d. Diese Punktnotation ist für das Auge angenehm

Vorschlag 5) sieht für mich sehr seltsam aus. Ich bevorzuge v'*A , damit explizit angegeben wird, dass Sie den Doppelvektor verwenden. Dies ist besonders wichtig in komplexen Vektorräumen, in denen das Dual nicht nur eine "Form" -Transformation ist.

Ich möchte @StefanKarpinski wiederholen, dass es ziemlich unglücklich wäre, unser prägnantes Sendeverhalten bei all dem zu verlieren. Wie lautet nach dieser Änderung die prägnante Syntax, um einen Vektor v und die Spalten der Matrix A durch diese Werte zu normalisieren? Derzeit kann man A ./ v' . Dies ist sehr gut für die Datenmanipulation.

Gute Fragen

Mein Schema schließt nicht aus, dass v'*A das komplexe Konjugat von v nimmt und durch A multipliziert
und all die verschiedenen anderen Fälle, die ich noch nicht ausdrücklich erwähnt habe, aber ohne weiteres konnte

wir könnten 5 eliminieren
Vielleicht ist das wünschenswert
Es entspricht nicht meiner Spaltenvektorregel

Diese Herangehensweise an den Rundfunk ist süß und klobig
Eine Lösung ist jetzt A ./ v[:,[1]]

Es hat den Vorteil zu dokumentieren, auf welcher Dimension gesendet wird
und verallgemeinert auf höherdimensionale Arrays

Oh, und die v[:,[1]] -Lösung hat die Tugend, das komplexe Konjugat NICHT zu nehmen
Das ist wahrscheinlich, was der Benutzer beabsichtigt .....

Ich mag diese zwei Beispiele, weil das erste ein LINEAR ALGEBRA-Beispiel ist
wo ein komplexes Konjugat sehr häufig gewünscht wird, aber das zweite Beispiel ist
Ein MULTIDIMENSIONALES DATENBEISPIEL, in dem Dinge in allen Dimensionen funktionieren sollen
nicht nur für Matrizen, und wir wollen höchstwahrscheinlich kein komplexes Konjugat

erfordert # 552. Dies ist das dritte Mal in den letzten zwei Wochen.

Ein weiterer Grund für die Unterscheidung von M [1,:] und M [:, 1] ist, dass derzeit unser Sendeverhalten dieses sehr bequeme Verhalten zulässt: M./sum(M,1) ist spaltenstochastisch und M./sum(M, 2) ist zeilenstochastisch. Dasselbe könnte für die Normalisierung getan werden, wenn wir die Normfunktion "korrigiert" hätten, um die Anwendung über Zeilen und Spalten einfach zu ermöglichen. Natürlich könnten wir immer noch Summen- (M, 1) und Summen- (M, 2) Rückgabematrizen anstelle von Auf- und Ab-Vektoren haben, aber das scheint ein bisschen anders zu sein.

Es scheint mir, dass das Sendeverhalten manchmal nett ist, aber Sie müssen diese zusätzlichen Einheiten genauso oft herausdrücken. Daher ist es in Ordnung, manchmal das Gegenteil zu tun, wenn der Rest des Systems besser ist (und ich denke, wenn Skalarabmessungen entfernt werden, wird das System schöner). Sie benötigen also eine Funktion wie

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

Dies ermöglicht Code wie M ./ widen(sum(M,2),2) oder A ./ widen(v,1) (siehe Beispiel @blakejohnson oben).

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

Ich bin mehr mit @blakejohnson in der Reduktionsfrage; Ich persönlich denke, es ist klarer, squeeze Dimensionen als widen sie. Ich vermute, ich würde ständig in den Dokumenten nachsehen, ob widen die Dimension am oder nach dem angegebenen Index einfügt, und die Nummerierung wird etwas komplexer, wenn Sie mehrere Dimensionen gleichzeitig erweitern möchten. (Was macht widen(v, (1, 2)) für einen Vektor v ?) Keines dieser Probleme ist für squeeze .

Unabhängig davon, ob wir standardmäßig erweitern oder drücken würden, denke ich, dass Julia dem Beispiel von numpy folgen sollte, wenn es um die Erweiterung geht, und so etwas wie v[:, newaxis] zulassen sollte. Aber ich glaube, dass ich es vorziehe, Dimensionen beizubehalten, anstatt sie zu verwerfen. Es ist schwieriger, einen Fehler zu finden, bei dem Sie versehentlich den falschen Weg verbreitert haben, als wenn Sie den falschen Weg gedrückt haben (was normalerweise zu einem Fehler führt).

In der Liste von @alanedelman
Ich fühle, dass

v * A gibt einen Vektor zurück, wenn A eine Matrix ist

ist nicht gut.

v_A sollte ein Fehler sein, wenn A nicht 1x1 ist (Nichtübereinstimmung des Indexbereichs)
v'_A sollte der richtige Weg sein, dies zu tun.

Eine Möglichkeit, dieses Problem zu beheben, besteht darin, den Vektor v automatisch in die nx1-Matrix zu konvertieren (falls erforderlich).
und behandle v 'immer als 1xn-Matrix (konvertiere das niemals in einen Vektor oder eine nx1-Matrix)
Außerdem können wir die 1x1-Matrix automatisch in eine Skalierungsnummer konvertieren (falls erforderlich).

Ich bin der Meinung, dass dies eine konsistente und einheitliche Art darstellt, über lineare Algebra nachzudenken. (gute Mathematik)

Eine einheitliche Methode zur Behandlung all dieser Probleme besteht darin, eine automatische (Typ?) Konvertierung zu ermöglichen (falls erforderlich).
zwischen Arrays der Größe (n), (n, 1), (n, 1,1), (n, 1,1,1) usw. (jedoch nicht zwischen Arrays der Größe (n, 1) und (1, n) )
(Genau wie wir bei Bedarf automatisch reelle Zahlen in komplexe Zahlen umwandeln)

In diesem Fall kann ein Array der Größe (1,1) (bei Bedarf) in eine Zahl konvertiert werden (siehe # 4797).

Xiao-Gang (ein Physiker)

Dies lässt jedoch v'_A ... Ich möchte wirklich, dass v'_A * w funktioniert

Mein Eindruck von linearer Algebra in Julia ist, dass sie sehr gut wie Matrixalgebra organisiert ist, obwohl Skalare und wahre Vektoren existieren (was ich gut finde!).

Lassen Sie uns überlegen, wie mit einem Produkt wie x*y*z*w , wobei jeder Faktor ein Skalar, ein Vektor oder eine Matrix sein kann, möglicherweise mit einer Transponierten darauf. Die Matrixalgebra definiert das Produkt von Matrizen, wobei eine Matrix die Größe n x m . Ein Ansatz wäre, diese Definition so zu erweitern, dass n oder m durch absent , was für die Berechnung des Produkts wie ein Wert von eins wirkt , wird aber verwendet, um Skalar und Vektoren von Matrizen zu unterscheiden:

  • Ein Skalar wäre absent x absent
  • Ein (Spalten-) Vektor wäre n x absent
  • Ein Zeilenvektor wäre absent x n

Im Idealfall möchten wir die Dinge so anordnen, dass wir niemals Zeilenvektoren darstellen müssen, aber es würde ausreichen, Operationen wie x'*y und x*y' zu implementieren. Ich habe das Gefühl, dass dies der Geschmack des Schemas ist, nach dem viele von uns suchen.

Aber ich fange an zu vermuten, dass das Verbot von Zeilenvektoren in dieser Art von Schema mit hohen Kosten verbunden sein wird. Beispiel: Überlegen Sie, wie wir ein Produkt in Klammern setzen müssten, um zu vermeiden, dass in einem Zwischenschritt ein Zeilenvektor gebildet wird: ( a ist ein Skalar, u und v sind Vektoren)

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

Um ein Produkt x*y'*z zu bewerten und dabei zu vermeiden, Zeilenvektoren zu erzeugen, müssten wir die Arten der Faktoren kennen, bevor wir die Multiplikationsreihenfolge auswählen! Wenn der Benutzer dies selbst tun sollte, scheint dies ein Hindernis für die generische Programmierung zu sein. Und ich bin mir nicht sicher, wie Julia es auf vernünftige Weise automatisch machen könnte.

Ein weiterer Grund, die Multiplikationsreihenfolge nicht im Voraus festzulegen: Ich erinnere mich an die Idee, mithilfe der dynamischen Programmierung die optimale Auswertungsreihenfolge von *(x,y,z,w) zu wählen, um die Anzahl der erforderlichen Operationen zu minimieren. Alles, was wir tun, um die Bildung von Zeilenvektoren zu vermeiden, wird dies wahrscheinlich stören.

Im Moment scheint mir die Einführung eines transponierten Vektortyps die vernünftigste Alternative zu sein. Das oder alles wie jetzt zu tun, außer nachfolgende Singleton-Dimensionen zu löschen, wenn sie beibehalten werden, würde zu einem Fehler führen.

Die Transposition ist nur ein besonderer Weg, um Modi zu permutieren. Wenn Sie v.' zulassen, wobei v ein Vektor ist, sollte permutedims(v,[2 1]) genau dasselbe zurückgeben. Entweder geben beide einen speziellen Zeilenvektortyp zurück oder sie führen eine neue Dimension ein.

Einen speziellen Typ für Zeilenvektoren zu haben, scheint mir keine gute Lösung zu sein, denn was werden Sie mit anderen Arten von Mode-n-Vektoren tun, z. B. permutedims([1:4],[3 2 1]) ? Ich fordere Sie dringend auf, auch die multilineare Algebra zu berücksichtigen, bevor Sie eine Entscheidung treffen.

@toivoh erwähnte das

"Ein Ansatz wäre, diese Definition so zu erweitern, dass n oder m durch abwesend ersetzt werden könnten, was für die Berechnung des Produkts wie ein Wert von eins wirkt, aber zur Unterscheidung von Skalar und Vektoren von Matrizen verwendet wird:

  1. ein Skalar würde fehlen x fehlen
  2. ein (Spalten-) Vektor würde nx fehlen
  3. ein Zeilenvektor würde fehlen xn "

In der multilinearen Algebra (oder für Tensoren mit hohem Rand) entspricht der obige Vorschlag der Verwendung der Abwesenheit zur Darstellung
Viele Indizes des Bereichs 1, dh die Größe (m, n, nicht vorhanden), können (m, n), (m, n, 1), (m, n, 1,1) usw. entsprechen.

Wenn wir diese Interpretation von abwesend verwenden, sind 1. und 2. in Ordnung und schön zu haben, aber 3. ist möglicherweise nicht in Ordnung.
Wir wollen keine Arrays der Größe (1, n) und (1,1, n) mischen.

Ich bin kein Spezialist für Tensortheorie, aber ich habe alle oben genannten Systeme (ohne Zusatzpakete) für umfangreiche Projekte mit linearer Algebra verwendet.

[TL; DR: Springe zu ZUSAMMENFASSUNG]

Hier sind die häufigsten Szenarien, in denen ich festgestellt habe, dass die Behandlung von Arrays allgemeiner sein muss als bei üblichen Matrixvektoroperationen:

(1) Funktionsanalyse: Sobald Sie beispielsweise das Hessische einer vektorwertigen Funktion verwenden, benötigen Sie Tensoren höherer Ordnung, um zu arbeiten. Wenn Sie viel Mathe schreiben, wäre es ein großer Schmerz, für diese Fälle eine spezielle Syntax verwenden zu müssen.

(2) Bewertungskontrolle: Zum Beispiel sollte man bei jedem Produkt, das berechnet werden kann, in der Lage sein, jede Untereinheit dieses Produkts separat zu berechnen, da man es möglicherweise mit mehreren verschiedenen Untereinheiten kombinieren möchte, um verschiedene Produkte zu bilden. Daher ist Toivos Sorge, dass beispielsweise a*u' verboten wird, nicht nur ein Kompilierungsproblem, sondern ein Programmierproblem. Eine noch häufigere Variante ist die Vorberechnung von x'Q zur Berechnung quadratischer Formen x'Q*y1 , x'Q*y2 , ... (wobei diese nacheinander ausgeführt werden müssen).

(3) Vereinfachen des Codes: Beim Umgang mit arithmetischen Operationen, die über mehrdimensionale Datensätze abgebildet werden, habe ich mehrmals festgestellt, dass 6-7 Zeilen unergründlichen Schleifen- oder Funktionszuordnungscodes in Systemen durch eine oder zwei kurze Array-Operationen ersetzt werden können die eine angemessene Allgemeinheit bieten. Viel besser lesbar und viel schneller.

Hier sind meine allgemeinen Erfahrungen mit den oben genannten Systemen:

MATLAB: Die Kernsprache ist über die üblichen Matrix-Vektor-Operationen hinaus begrenzt, sodass normalerweise Schleifen mit Indizierung geschrieben werden.

NumPy: Allgemeinere Funktionen als MATLAB, aber chaotisch und kompliziert. Für fast jede nicht triviale Probleminstanz musste ich mich auf die Dokumentation beziehen, und selbst dann stellte ich manchmal fest, dass ich selbst eine Array-Operation implementieren musste, die meiner Meinung nach intuitiv hätte definiert werden müssen. Es scheint, dass es so viele verschiedene Ideen im System gibt, dass jeder Benutzer und Entwickler Probleme haben wird, automatisch zu erraten, wie der andere über etwas denken wird. Es ist normalerweise möglich, einen kurzen, effizienten Weg zu finden, aber dieser Weg ist für den Autor oder Leser nicht immer offensichtlich. Insbesondere bin ich der Meinung, dass die Notwendigkeit der Erweiterung und der Singleton-Dimensionen nur einen Mangel an Allgemeinheit bei der Implementierung für die Anwendung von Operatoren widerspiegelt (obwohl einige dies möglicherweise intuitiver finden).

Mathematica: Sauber und sehr allgemein - insbesondere sind alle relevanten Operatoren für das Tensorverhalten höherer Ordnung ausgelegt. Siehe neben Dot beispielsweise die Dokumente zu Transponieren, Reduzieren / Partitionieren und Inner / Außen. Wenn Sie nur diese Operationen kombinieren, können Sie bereits die meisten Anwendungsfälle für das Array-Jonglieren abdecken. In Version 9 werden der Kernsprache sogar zusätzliche Tensoralgebra-Operationen hinzugefügt. Der Nachteil ist, dass die Mathematica-Methode zwar sauber und sinnvoll ist (wenn Sie die Sprache kennen), aber möglicherweise nicht der üblichen mathematischen Notation entspricht. Und natürlich macht es die Allgemeinheit schwierig zu wissen, wie der Code funktioniert.

scmutils: Für die Funktionsanalyse ist es sauber, allgemein und bietet die mathematisch intuitivsten Operationen (sowohl Schreiben als auch Lesen) der oben genannten. Die Up / Down-Tupel-Idee ist eigentlich nur eine konsistentere und allgemeinere Erweiterung dessen, was Menschen in der schriftlichen Mathematik häufig mit Transponierungszeichen, Differenzierungskonventionen und anderen semi-standardisierten Begriffen tun. aber alles funktioniert einfach. (Um meine Doktorarbeit zu schreiben, entwickelte ich eine konsistente und eindeutige Notation, die der traditionellen mathematischen Notation ähnelt, aber der SICM-Syntax von Sussman & Wisdom isomorph ist.) Sie haben sie auch für eine Implementierung mit Differentialgeometrie verwendet [1] inspirierte einen Port zu SymPy [2]. Ich habe es nicht für die Datenanalyse verwendet, aber ich würde erwarten, dass Sie in einem generischen Array-Kontext, in dem Sie nur eine Art von Tupel (wie Mathematicas Liste) wollten, nur eines ("up") gemäß Konvention auswählen können. Auch hier verschleiert die Allgemeinheit die Leistungsaspekte für den Programmierer, aber ich würde hoffen, dass dies ein Bereich ist, in dem Julia herausragende Leistungen erbringen kann.

ZUSAMMENFASSUNG

Ich denke, der vorgeschlagene transponierte Vektortyp sollte als das allgemeinere "down" -Tupel in scmutils charakterisiert werden, während reguläre Vektoren die "up" -Tupel sind. Sie so etwas wie "Vektor" und "transponierter Vektor" zu nennen, wäre für Menschen wahrscheinlich sinnvoller, als sie "auf" und "ab" (auf Kosten der Kürze) zu nennen. Dies würde drei Verwendungskategorien unterstützen:

(1) für die Datenanalyse benötigen Personen, die nur verschachtelte Arrays wünschen, nur "Vektor";
(2) für die lineare Matrixvektor-Algebra können Menschen "Vektor" und "transponierter Vektor" in direkter Entsprechung mit der mathematischen Konvention verwenden ("Matrix" wäre äquivalent zu einem "transponierten Vektor" von "Vektor");
(3) Für Tensoroperationen höherer Ordnung (wo es weniger Standardisierung gibt und die Leute normalerweise sowieso denken müssen) sollte die Implementierung die volle Allgemeinheit des Zwei-Arten-Tupel-Arithmetiksystems unterstützen.

Ich glaube, dieser Ansatz spiegelt den oben aufkommenden Konsens über die Ergebnisse verschiedener Operationen wider, mit der Ausnahme, dass die Fälle, in denen frühere Beiträge Fehler berücksichtigten ( v' und v*A ), tatsächlich sinnvoll (und oft nützlich) sind ) Ergebnisse.

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

@thomasmcoffee klingt so, als würden Sie sich für eine explizite Unterscheidung zwischen co- und kontravarianten Vektoren einsetzen.

Ich würde das als eine übliche Anwendung betrachten, aber zu spezifisch für das, was ich befürworte: Für mich hat das eine geometrische Bedeutung, die eine Beschränkung auf rechteckige Zahlentensoren (für Koordinatendarstellungen) impliziert. Da ich mir (ohne Fachkenntnisse auf diesem Gebiet) vorstelle, dass eine geeignete Bibliothek von Tensoralgebrafunktionen mit Standardarrays normalerweise für diesen Zweck ausreichen würde, bin ich mit Alans Argument einverstanden, dass dies allein nicht zwingend genug ist, um ein Zwei-Arten-System einzuführen die Kernsprache.

Ich denke hauptsächlich an andere Anwendungen, die von einer allgemeineren verschachtelten Struktur abhängen, zum Beispiel an die Berechnung von Funktionen mehrerer Argumente gemischter Dimensionalität, die später als "Add-On" schwieriger zu entwickeln wären, wenn die Kernsprache dies nicht unterstützen würde diese Unterscheidung. Vielleicht meinen wir das Gleiche.

Das Problem mit Aufwärts- und Abwärtsvektoren besteht darin, dass Sie die Idee auf allgemeine Arrays erweitern müssen. Andernfalls werden Vektoren zu etwas Besonderem und von Arrays getrennt, anstatt nur zum eindimensionalen Fall eines Arrays, was zu einem ganzen Durcheinander schrecklicher Probleme führen würde. Ich habe viel darüber nachgedacht, wie das geht, aber keine akzeptablen gefunden. Wenn Sie gute Ideen haben, wie Sie Auf- und Ab-Vektoren auf Arrays verallgemeinern können, würde ich sie gerne hören.

Ich versuche nur, diese Idee zu extrapolieren. Soweit ich weiß, müssen Sie für jede Dimension angeben, ob sie nach oben oder nach unten zeigt, um mit einem Array mit Aufwärts- und Abwärtsvektoren zu berechnen. Im Allgemeinen könnte dies erreicht werden, indem ein Array in so etwas eingeschlossen wird

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

Dabei wäre UPMASK eine Bitmaske, um anzuzeigen, welche Dimensionen aktiv sind. Dann könnten Operationen an nicht umschlossenen Arrays implementiert werden, indem ein Standardwert von UPMASK als Funktion von N bereitgestellt wird: Vektoren würden standardmäßig ein einzelnes Up, Matrizen das erste Up und das zweite Down; dann bin ich mir nicht sicher, wie es vernünftigerweise fortgesetzt werden würde.

Einige zufällige Gedanken:

  • Würden quadratische / bilineare Formen besser durch zwei Abwärtsdimensionen dargestellt werden?
  • Wenn Transponieren nur dem Umdrehen der Auf- / Abwärtsbewegung jeder Dimension entsprechen würde, würden wir wahrscheinlich auch einen transponierten Matrixtyp mit der ersten Dimension nach unten und der zweiten nach oben erhalten.
  • Aufwärtsmuster, die dem Standard entsprechen, können direkt durch das zugrunde liegende Array dargestellt werden, anstatt es zu verpacken.

Nun, dies ist sicherlich eine Verallgemeinerung des Typs Transposed , und sie hat sicherlich einige Vorteile. Ich bin mir nicht sicher, ob es machbar ist.

Ich denke, Toivos Vorschlag ist eine vernünftige Erkenntnis dessen, was ich oben befürwortet habe. Für mich ist es am sinnvollsten, die Richtungswechsel bei höheren Ordnungen fortzusetzen: Wenn beispielsweise jemand die Komponenten einer Potenzreihe als nicht umhüllte Arrays bereitstellt, würde dies das Richtige tun.

Bei weiterer Überlegung denke ich jedoch, dass es sehr wirkungsvoll sein könnte, beide Ideen zu kombinieren: (1) Unterscheidungen zwischen Aufwärts- und Abwärtsvektoren und (2) Unterscheidungen zwischen Arrays und Vektoren. Dann könnten Sie Objekte haben, bei denen einige Dimensionen höher, andere niedriger und andere "neutral" sind. Der Grund für die Unterscheidung von Arrays und Vektoren besteht darin, dass Arrays semantisch zur Organisation dienen (Sammeln mehrerer Dinge derselben Art), während Vektoren zur Koordinierung dienen (Repräsentieren mehrdimensionaler Räume). Die Fähigkeit, beide Unterscheidungen in einem Objekt zu kombinieren, besteht darin, dass beide Zwecke gleichzeitig erfüllt werden können. Die neutralen Dimensionen würden gemäß den Rundfunkregeln behandelt, während die Auf- / Ab-Dimensionen gemäß den Tensor-Arithmetikregeln behandelt würden.

Zurück zu einem früheren Beispiel von mir: Angenommen, Sie berechnen eine Reihe quadratischer Formen x'Q*y1, x'Q*y2, ... für verschiedene Vektoren y1, y2, ... . Bezeichnen Sie nach SICM Tupel (Spaltenvektoren) mit (...) und Tupel (Zeilenvektoren) mit [...] . Wenn Sie dies alles auf einmal tun möchten und nur mit Auf / Ab zu tun haben, besteht die herkömmliche Methode darin, die yi Y = [y1, y2, ...] mit einem Abwärts-Tupel (von) zu einer Matrix r = x'Q*Y , wodurch Sie die Ergebnisse in einem down tuple r . Aber was ist, wenn Sie jedes dieser Ergebnisse mit einem (Spalten-) Vektor v multiplizieren möchten? Sie können nicht einfach r*v , da Sie eine Kontraktion erhalten (Punktprodukt). Sie können r in ein Up-Tupel konvertieren und dann multiplizieren, wodurch Sie Ihre Ergebnisse in einem Up-Tupel (von Up-Tupeln) erhalten. Aber nehmen wir an, Sie brauchen für den nächsten Schritt ein Down-Tupel? Semantisch gesehen haben Sie eine Dimension, die Ihre Berechnung durchläuft und nur eine Sammlung von Dingen darstellt, die Sie immer senden möchten. Um dies jedoch in der streng auf / ab-Welt zu erreichen, müssen Sie weiterhin willkürliche kontextabhängige Konvertierungen durchführen, um das richtige Verhalten hervorzurufen.

Angenommen, Sie haben auch neutrale Tupel (Arrays) mit der Bezeichnung {...} . Dann schreiben Sie natürlich ys = {y1, y2, ...} als Array (von bis zu Tupeln), so dass r = x'Q*ys ein Array ist und r*v auch ein Array (von bis zu Tupeln) ist. Alles macht Sinn und es sind keine willkürlichen Konvertierungen erforderlich.

Stefan schlägt vor, dass die Unterscheidung von 1-D-Arrays von Aufwärts- / Abwärtsvektoren katastrophal ist, aber ich denke, dieses Problem wird durch die Tatsache gelöst, dass die meisten Funktionen sinnvoll sind, wenn Vektoren _oder_ auf Arrays, aber NICHT _ehrner_ betrieben werden. (Oder auf Matrizen _oder_ auf Arrays von Vektoren _oder_ auf Vektoren von Arrays _oder_ auf Arrays von Arrays, aber NICHT _either_. Und so weiter.) Mit geeigneten Konvertierungsregeln habe ich mir keinen gemeinsamen Fall vorgestellt, der dies nicht tun würde das richtige automatisch. Vielleicht kann jemand?

Bei einem genaueren Blick [1] stellte ich fest, dass scmutils tatsächlich das, was sie "Vektoren" nennen, von Auf- und Ab-Tupeln unter der Haube unterscheidet. Derzeit sind die Konvertierungsregeln jedoch so eingerichtet, dass diese "Vektoren" bei jedem Eintritt in die Up / Down-Welt auf Up-Tupel abgebildet werden (wie ich zuvor vorgeschlagen hatte), mit der Einschränkung, dass "wir uns das Recht vorbehalten, diese Implementierung zur Unterscheidung zu ändern Schema-Vektoren von Up-Tupeln. " (Vielleicht kann jemand auf dem Campus GJS fragen, ob er bestimmte Ideen hat.) Das Sage-System [2] trennt die Handhabung von Arrays weitgehend von Vektoren und Matrizen (derzeit keine Kernunterstützung für Tensoren) und die einzigen Probleme, die ich hatte Dies hat mit dem Mangel an eingebauter Konvertierung zwischen ihnen in Fällen zu tun, die offensichtlich Sinn machen würden.

[1] http://groups.csail.mit.edu/mac/users/gjs/6946/refman.txt --- beginnend mit "Strukturierte Objekte"
[2] http://www.sagemath.org/

Ich sprach mit @jiahao am Mittagstisch und er erwähnte, dass das Julia-Team versuchte herauszufinden, wie man lineare

Betrachten wir im Moment nur das Produkt zwischen zwei Arrays. Andere Operationen haben eine ähnliche Verallgemeinerung. Die drei häufigsten Arten von Produkten beim Umgang mit Arrays sind das äußere Produkt, das innere Produkt und das elementweise Produkt. Wir denken normalerweise daran, solche Operationen zwischen zwei Objekten durchzuführen, wie z. B. inner(A,B) oder A*B . Wenn diese Operationen für höherdimensionale Arrays ausgeführt werden, werden sie jedoch nicht zwischen den Arrays als Ganzes ausgeführt, sondern zwischen bestimmten Dimensionen der Arrays. Mehrere äußere / innere / elementweise Unteroperationen finden in einer einzigen Operation zwischen zwei Arrays statt, und jede Dimension jedes Arrays muss genau einer Unteroperation zugewiesen werden (entweder explizit oder einer Standardeinstellung). Für die inneren und elementweisen Produkte muss eine Dimension links mit einer gleich großen Dimension rechts gepaart werden. Die äußeren Produktabmessungen müssen nicht gepaart werden. Meistens macht der Benutzer entweder ein inneres Produkt oder ein elementweises Produkt zwischen einem Paar von Dimensionen und einem äußeren Produkt für alle anderen. Das äußere Produkt ist eine gute Standardeinstellung, da es am häufigsten verwendet wird und nicht gekoppelt werden muss.

Normalerweise denke ich, dass die Dimensionen eher benannt als geordnet sind, ähnlich wie die x-, y- und z-Achsen eines Diagramms. Wenn Sie jedoch möchten, dass Benutzer tatsächlich durch geordnete Indizierung auf die Arrays zugreifen können (z. B. A[1,2,5] statt A[a1=1, a3=5, a2=2] ), müssen Sie über eine konsistente Prozedur verfügen, um die Ergebnisse einer Operation zu ordnen. Ich schlage vor, das Ergebnis zu ordnen, indem alle Dimensionen des ersten Arrays aufgelistet werden, gefolgt von der Auflistung aller Dimensionen des zweiten Arrays. Alle Dimensionen, die an einem inneren Produkt beteiligt waren, werden herausgedrückt, und für Dimensionen, die an einem elementweisen Produkt beteiligt waren, wird nur die Dimension aus dem zweiten Array herausgedrückt.

Ich werde mir dafür eine Notation ausdenken. Fühlen Sie sich frei, Juliafy es. Sei A ein Array, das a1 von a2 von a3 und sei B ein Array, das b1 um b2 . Nehmen wir an, array_product(A, B, inner=[2, 1], elementwise=[3, 2]) würde das innere Produkt zwischen den Dimensionen a2 und b1 , das elementweise Produkt zwischen a3 und b2 und dem äußeres Produkt von a1 . Das Ergebnis wäre ein Array, das a1 mal a3 .

Es sollte klar sein, dass kein binärer oder unärer Operator im Kontext von Arrays mit höheren Dimensionen eine große Bedeutung haben wird. Sie benötigen mehr als zwei Argumente, um anzugeben, was mit jeder Dimension geschehen soll. Sie können jedoch die Leichtigkeit der linearen Algebra wiedererlangen, indem Sie die Matlab-Operatoren für Array-Operationen in nur den ersten beiden Dimensionen abkürzen:

Matlabs A*B ist array_product(A, B, inner=[2,1]) .

Matlabs A.' ist permute(A, B, [2,1]) wobei permute alle Dimensionen über der Anzahl des dritten Arguments unverändert lässt.

Sie können wählen, ob Fehler ausgegeben werden sollen, wenn die Dimensionalität der Arrays größer als 2 oder sogar ungleich 2 ist, wie dies Mathematica bei Vektortransponierungen tut. Wenn Sie nur die allgemeinen Array-Berechnungen verwenden, müssen Sie nicht entscheiden, ob Sie den Vorschlag von möchten , alle (n, m) Arrays als (n, m, 1) und (n, m, 1) zu interpretieren 1). Nur wenn Sie die linearen Algebraoperatoren oder andere Operatoren verwenden, die ein Array oder eine bestimmte Dimensionalität erwarten, müssen Sie diese Entscheidung treffen. Ich mag den Vorschlag von @wenxgwen , weil eine dynamisch getippte Sprache wenig Nachteile hat.

Ich habe eine detailliertere Beschreibung meines Systems geschrieben , die Addition, Exponentiation und Differenzierung zusammen mit der Kettenregel und der Produktregel für die Array-Berechnung umfasst.

Danke für die Perspektive! Ich fand das ziemlich aufschlussreich, um zu verstehen, was für ein Biest ein allgemeines Array * Array-Produkt wirklich ist.

Es kann interessant sein, die Vorschläge für die mehrdimensionale Array-Multiplikation mit der in PEP 0465 für einen Matrixmultiplikationsoperator vorgeschlagenen Semantik zu

1d-Vektoreingaben werden durch Voranstellen oder Anhängen einer '1' an die Form auf 2d hochgestuft, die Operation wird ausgeführt, und dann wird die hinzugefügte Dimension aus der Ausgabe entfernt. Die 1 wird immer an der "Außenseite" der Form hinzugefügt: für linke Argumente vorangestellt und für rechte Argumente angehängt. Das Ergebnis ist, dass Matrix @ Vektor und Vektor @ Matrix beide legal sind (unter der Annahme kompatibler Formen) und beide 1d-Vektoren zurückgeben; vector @ vector gibt einen Skalar zurück ... Eine Infelizität dieser Definition für 1d-Vektoren besteht darin, dass @ in einigen Fällen nicht assoziativ ist ((Mat1 @ vec) @ Mat2! = Mat1 @ (vec @ Mat2)). Dies scheint jedoch ein Fall zu sein, in dem Praktikabilität die Reinheit übertrifft

Das Tippen in Python verursacht ein besonderes Problem. Natürlich sollten Arrays und Matrizen austauschbar sein (dieselben zugrunde liegenden Daten). Da Python jedoch von der Überprüfung eines Typs abrät, werden Matrizen am Anfang einer Funktion, die ein Array erwartet, nicht in die richtige Schnittstelle umgewandelt und umgekehrt. Aus diesem Grund müssen sie unterschiedliche Operatorzeichen haben. Julia mit Laufzeit-Typprüfung und convert -Methoden leidet nicht unter dieser Mehrdeutigkeit.

Aus PEP 0465:

Eine Infelizität dieser Definition für 1d-Vektoren besteht darin, dass @ in einigen Fällen nicht assoziativ ist ((Mat1 @ vec) @ Mat2! = Mat1 @ (vec @ Mat2)).

Insbesondere kann diese Art der Definition in Mathematica zu falschen Ergebnissen führen, da Dot ( . Flat ) bei symbolischer Bewertung (wie bei f ) als assoziativ ( Flat ) angenommen wird f unten, aber nicht mit 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

Von @drhagen :

Julia mit Laufzeit-Typprüfung und convert -Methoden leidet nicht unter dieser Mehrdeutigkeit.

Aus diesem Grund halte ich es für die richtige Lösung für Julia, die Daten selbst zwischen Array-ähnlicher Semantik (für Universal Broadcasting) und Tensor-ähnlicher Semantik (für mögliche Kontraktion) unterscheiden zu lassen.

Ich bin hier keineswegs eine Autorität, aber ich denke nicht, dass der allgemeine willkürlich dimensionale Sammlungstyp ( Array ) einen Operator unterstützen sollte, der Punktprodukte ausführt. Dieser Operator kann für diesen Typ einfach nicht sinnvoll definiert werden, da das Punktprodukt zwischen zwei beliebigen Dimensionen liegen kann und zusätzliche Argumente erforderlich sind, die einem binären Operator nicht zur Verfügung gestellt werden können. Das gleiche gilt für alle linearen Algebraoperationen, inv , transpose usw.

Um im mathematischen Bereich der linearen Algebra arbeiten zu können, sollten drei weitere Typen vorhanden sein: Matrix , ColumnVector und RowVector , auf denen alle normalen linearen Algebraoperatoren und -funktionen ausgeführt werden wie gewohnt arbeiten.

Nachdem die Typstruktur nun gut definiert ist, können Sie es dem Benutzer erleichtern, indem Sie eine implizite Konvertierung für Matrix zu Array{2} , ColumnVector zu Array{1} hinzufügen. und RowVector bis Array{2} (nicht sicher), Array{2} bis Matrix und Array{1} bis ColumnVector .

Mein obiger Vorschlag (https://github.com/JuliaLang/julia/issues/4774#issuecomment-32705055) ermöglicht es jeder Dimension einer mehrdimensionalen Struktur zu unterscheiden, ob sie neutral ist ("Sammlung" / "Array"), up (" Spalte ") oder Abwärtssemantik (" Zeile "). Ich denke, was Sie beschreiben, ist dann ein Sonderfall.

Der Vorteil dieses allgemeinen Ansatzes besteht darin, dass Sie selbst bei Berechnungen mit vielen Daten- oder Raumdimensionen die Bediener dazu bringen können, genau das Richtige zu tun, ohne explizit anzugeben, auf welchen Dimensionen sie arbeiten sollen. Ich denke, wir sind uns einig, dass es zumindest in Julia für einen Benutzer viel intuitiver und lesbarer ist, einmal die Bedeutung der Eingabedaten durch Auswahl von Typparametern anzugeben, als die Bedeutung jeder Operation durch Aufrufen jeder Instanz mit angeben zu müssen zusätzliche Argumente für Dimensionsindizes. Implizite oder explizite Konvertierungen können weiterhin mit volldimensionaler Allgemeinheit verwendet werden, wenn die Bedeutung auf ungewöhnliche Weise im Midstream geändert werden muss.

@ Thomasmcoffee Ich mag Ihren Vorschlag sehr. Ich habe etwas vage Ähnliches in ein DSL (vor langer Zeit, weit weg) mit ein paar Leitprinzipien (auch bekannt als persönliche Meinungen) implementiert:

  1. Der Begriff der Dualen als verschieden ist für jede vernünftig selbstkonsistente Semantik von entscheidender Bedeutung.
  2. Die Ad-hoc-Durchsetzung der Tensoralgebra mit parametrisierten Operatoren (oder irgendetwas außerhalb der Daten) ist ästhetisch sehr unangenehm.

Die größten Beschwerden, die ich damals bekam (und die laut waren), betrafen genau die Art von Unannehmlichkeiten, die Ihre dreiwertige Semantik (Hinzufügen eines neutralen Sammlungsbegriffs) löst. Nett! Diese Idee ist mir nie in den Sinn gekommen, macht aber jetzt so viel Sinn, dass Sie sie dort veröffentlichen. Ich würde wirklich gerne ein solches System verwenden, und ich meine für echte Arbeit. Es wäre schön, wenn Julia dies aufnehmen könnte!

Was ihr zu beschreiben scheint, sind reguläre Tensoren. Ich bezweifle, dass dies ein häufig genug verwendeter Anwendungsfall ist, um die Aufnahme in die Standardbibliothek zu rechtfertigen, da die beiden anderen Anwendungsfälle (Sammlungen und lineare Algebra) weitaus häufiger sind. Wenn es jedoch nahtlos integriert werden könnte, würde ich es unterstützen. Können Sie einige Beispiele dafür nennen, wie einige gängige Operationen unter diesem System aussehen würden, wie z. B. Vektormatrixmultiplikation, Skalararray-Multiplikation, Verteilen der Addition eines Arrays auf ein Array von Arrays usw.?

Ich denke du hast recht David. Wir sprechen wirklich über zwei Anwendungsfälle.

Die Teilmenge der linearen Algebra wird von den meisten Menschen wie Ihnen am häufigsten benötigt
sagen. Auch dort befürworte ich die Unterscheidung zwischen v und v '.

Was ich wirklich gerne hätte (Offenlegung der Gier hier einfügen), ist Tensors mit
erstklassiger (oder geschlossener) Status ... in der Nähe der nativen Geschwindigkeit (im Grenzfall,
im Vergleich zur linearen Algebra-Leistung) mit einfacher Syntax, nicht überarbeitet
Tippprobleme, bei denen Co / Contravarianz in den Daten codiert ist, werden nicht aufgezwungen
die Betreiber. Sobald ich die Datensemantik definiert habe, sollten die Operationen
arbeite einfach. Tensoral Duck Typing.

Vielleicht gehören Tensoren und TDT in ein Paket und nicht in den Kern, nur auf
Gründe der relativen Popularität. Aber wie die Julia-Erklärung von
Unabhängigkeit sagt, Julia ist aus Gier geboren. Und wie Gordon Gecko sagt:
Gier ist gut. :) :)
Am 21. März 2014, 03:14 Uhr schrieb "David Hagen" [email protected] :

Was ihr zu beschreiben scheint, sind reguläre Tensoren. Ich bezweifle, dass das ein ist
Der Anwendungsfall ist häufig genug, um zu rechtfertigen, dass er sich in der Standardbibliothek befindet
Die beiden anderen Anwendungsfälle (Sammlungen und lineare Algebra) sind weitaus mehr
verbreitet. Wenn es jedoch nahtlos integriert werden könnte, würde ich es unterstützen.
Können Sie einige Beispiele dafür nennen, wie einige gängige Operationen aussehen würden?
unter diesem System, wie Vektor-Matrix-Multiplikation, Skalar-Array
Multiplikation, Verteilung der Addition eines Arrays über ein Array von
Arrays usw.?

Antworten Sie direkt auf diese E-Mail oder sehen Sie sie sich auf Gi tHubhttps an: //github.com/JuliaLang/julia/issues/4774#issuecomment -38262998
.

Ich denke, eine nahtlose Integration ist bei einer ausreichend reichen Typenfamilie definitiv erreichbar. Wenn Sie Toivos https://github.com/JuliaLang/julia/issues/4774#issuecomment -32693110 oben erweitern, könnte es fiktiv so beginnen:

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

(Derzeit ist AbstractMatrix{T} einfach ein Aliase für AbstractArray{T, 2} . Möglicherweise könnte hier ein anderer Name verwendet werden.)

Von hier aus scheinen folgende Implementierungen logisch:

  1. Die verallgemeinerte transpose -Methode tauscht nach dem Neuanordnen der Dimensionen und der entsprechenden UPMASK- und DOWNMASK-Indizes UPMASK und DOWNMASK aus. Neutrale Abmessungen wären davon nicht betroffen.
  2. Alle AbstractArray{T, N} Subtypen werden normalerweise standardmäßig in entsprechende alternierende AbstractTensorArray{T, N, [..., false, true, false, true], [..., true, false, true, false]} Subtypen in Tensoroperationen konvertiert. Dadurch bleibt die vorhandene Semantik von Julias spezieller Array-Syntax für Vektoren und Matrizen erhalten.
  3. Eine Konstruktormethode (z. B. array ) für AbstractTensorArray wird verwendet, um neutrale Dimensionen zu erzeugen, und kann andere AbstractTensorArray s (oder darin konvertierbare Typen) kombinieren, um ein kombiniertes AbstractTensorArray zu erstellen

Betrachtet man die Beispiele von

Vektormatrix-Multiplikation, Skalar-Array-Multiplikation

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}

(unter Verwendung von Matrix mit einer Definition, die der obigen Definition von AbstractMatrix )

Verteilen der Hinzufügung eines Arrays auf ein Array von Arrays

Ich verstehe dies semantisch als Addition eines Vektors über ein Array von Vektoren, Addition einer Matrix über ein Array von Matrizen und so weiter:

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

In Anbetracht meines früheren Beispiels der Skalierung eines Vektors v durch mehrere verschiedene quadratische Formen x'M*w1, x'M*w2, ... für ein Endergebnis 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])

In dieser fiktiven Implementierung habe ich angenommen, dass AbstractArray in Ruhe gelassen wird und somit AbstractTensorArray einen eigenen "Raum" in der Typhierarchie bildet. Die Dinge könnten vereinfacht werden, wenn die gesamte AbstractArray -Familie einfach durch AbstractTensorArray , aber das ist eine andere Diskussion.

Im Kontext eines Pakets für etwas in der Quantenphysik habe ich mit der Definition meines eigenen Tensortyps (eigentlich mehr als einer) herumgespielt. Anfangs hatte ich auch eine Vorstellung davon, dass Indizes in zwei Varianten erhältlich sind (auf und ab, eingehend und ausgehend, kovariant und kontravariant, wie auch immer Sie es nennen möchten), wobei diese Informationen entweder in einem Feld des Typs oder sogar in einem gespeichert werden Parameter eingeben. Nach einer Weile entschied ich, dass dies einfach zu mühsam ist. Es ist viel einfacher, die Indizes des Tensors einfach einem Vektorraum zuzuordnen (was ich sowieso schon getan habe) und diesem Vektorraum ein anderes Dual zu ermöglichen. In der Praxis meine ich mit Vektorraum nur einen einfachen Julia-Typ, der die Dimension des Raums umschließt und ob es sich um den dualen handelt oder nicht. Wenn ein Tensorindex einem normalen Vektorraum zugeordnet ist, handelt es sich um einen Aufwärtsindex. Wenn er einem Doppelindex zugeordnet ist, handelt es sich um einen Abwärtsindex. Wenn Sie mit Tensoren / Arrays arbeiten möchten, für die es keinen Unterschied gibt, definieren Sie einfach einen anderen Vektorraumtyp, der nicht zwischen dem normalen Vektorraum und seinem Dual unterscheidet.

In diesem Vorschlag können Sie nur Tensorindizes kontrahieren, die Vektorräumen zugeordnet sind, die jeweils dual sind. ctranspose (= hermitische Konjugation) bildet den Vektorraum jedes Index auf sein Dual ab (zusammen mit der Permutation der Indizes im Fall einer Matrix und einer bevorzugten Definition für Tensoren höherer Ordnung) usw.

Natürlich sind normale Transposition und komplexe Konjugation in dieser Umgebung nicht wirklich gut definiert (dh dies sind keine basenunabhängigen Konzepte).

Minimalistisch sieht es ungefähr so ​​aus:

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

Natürlich könnten Sie eine Syntax erfinden, um nicht ständig Space schreiben zu müssen. Sie können einen anderen Raumtyp erstellen, für den dual (s) == s ist, um Tensoren zu haben, die nicht zwischen Aufwärts- und Abwärtsindizes unterscheiden, und so weiter. Aber natürlich gibt es keine Möglichkeit, dies in Julias Standard-Array-Typ zu integrieren, ohne alles zu beschädigen ...

Ich habe mich immer gefragt, warum es keinen engeren Zusammenhang zwischen der Verwendung von Tensoren in der Technik / Physik und dem Umgang mit mathematischen Softwareprogrammen gibt. Ich fand eine interessante Stapelaustausch-Konversation zum Thema ... http://math.stackexchange.com/questions/412423/differences-between-a-matrix-and-a-tensor. Auch hier war ein guter Nachschlagewerk.
http://www.mat.univie.ac.at/~neum/physfaq/topics/tensors

Ich benutze Matlab viel für mein tägliches wissenschaftliches Rechnen, aber ein Neuling bei Julia. Hier stelle ich fest, dass es viele Diskussionen über hochdimensionale Array-Multiplikation und -Transposition oder andere ähnliche Array-Operationen gibt. Ich schlage vor, einen Blick auf http://www.mathworks.com/matlabcentral/fileexchange/8773-multiple-matrix-multiplications--with-array-expansion-enabled zu werfen

Es folgt im Wesentlichen der Syntax, die der von @drhagen in einem früheren Beitrag erwähnten ähnelt, z. B. array_product (A, B, inner_A_dim = [1, 2], inner_B_dim = [3, 4]) für ein Produkt zwischen den Arrays A und B. die gegebenen Innenmaße.

Dies ist ein Matlab-Paket, das Multiplikationen oder Transpositionsoperationen auf einige ausgewählte Dimensionen anwenden kann. Das Paket enthält ein Handbuch zur Implementierung dieser Operationen in Matlab, aber ich denke, die mathematische Theorie sollte auch für andere Sprachen gelten. Ihre Idee ist es, Array-Operationen zu implementieren, indem sie die Verwendung von For-Schleifen vermeiden und sich hauptsächlich auf die Umformung von Arrays usw. verlassen. In Matlab ist es also extrem schnell. Ich weiß nicht, ob Julia eher vektorisierten oder devektorisierten Operationen ähnelt (scheint die spätere zu sein). Ich bin der Meinung, dass eine vektorisierte Operation ein Vorteil für hochdimensionale Array-Operationen ist, wenn der Kern dies unterstützt. Vielleicht sollten wir diese Art von Array-Operationen in dieser Phase aufrichtig in Betracht ziehen.

Als Referenz: Eine weitere ähnliche Implementierung in Matlab für den INV-Betrieb finden Sie hier: http://www.mathworks.com/matlabcentral/fileexchange/31222-inversion-every-2d-slice-for-arbitrary-multi-dimension-array

Beachten Sie auch, dass nach der Veröffentlichung des Support-Pakets für den Matlab-Array-Betrieb im Jahr 2005 der Download-Datensatz bis heute auf einem hohen Niveau gehalten wird. Wie in meiner praktischen Erfahrung werden Array-Operationen in der Physik und anderen Bereichen sehr häufig verwendet. Ich würde sagen, wenn Julia ähnliche Funktionen zum Betreiben von Arrays mit beliebigen Größen hat, wird das Spiel sehr interessant!

eine weitere Abstimmung hier für @alanedelmans Lösungsvorschlag nach oben. Hier ist ein motivierendes Beispiel.

Im Moment ist ein Zeilen-Slice ein 2d-Array, während ein Spalten-Slice ein 1d-Array ist. das ist seltsam asymmetrisch und hässlich:

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

Insbesondere bedeutet dies, dass ich eine Zeile nicht mit einer Spalte multiplizieren und eine Zahl extrahieren kann, ohne eine schrecklich hässliche Manipulation durchzuführen

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

Es ist kein kohärenter Vorschlag, es sei denn, Sie machen '* einem speziellen Operator, was ziemlich zweifelhaft ist, da x'*y und (x')*y nicht dasselbe bedeuten. Darüber hinaus würde die Multiplikation nicht assoziativ sein.

Ich verstehe die Schwierigkeiten mit x_y 'und y'_x. Es kann besser sein, zu behandeln
innere und äußere Produkte als getrennte Operationen, z. B. dot (). (Vielleicht
auch mit cdot?)

Aber was sind die Argumente für eine Scheibe entlang der ersten
dimension gibt ein Objekt zurück, dessen Dimension sich von einem Slice entlang unterscheidet
die zweite Dimension? Aus Gründen der Konsistenz scheint es, dass jedes Mal, wenn Sie schneiden,
Die Dimension des resultierenden Objekts sollte um eins verringert werden.

Am Mittwoch, den 16. Juli 2014 um 20:17 Uhr meldete Stefan Karpinski [email protected]
schrieb:

Es ist kein kohärenter Vorschlag, es sei denn, Sie machen '* zu einem speziellen Operator, der
ist ziemlich zweifelhaft, da x'_y und (x ') _ y nicht dasselbe bedeuten.
Darüber hinaus würde die Multiplikation nicht assoziativ sein.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/JuliaLang/julia/issues/4774#issuecomment -49254346.

Madeleine Udell
Doktorand in Computer- und Mathematikingenieurwesen
Universität in Stanford
www.stanford.edu/~udell

@madeleineudell , ich stimme Ihnen zu, aber das ist ein anderes Problem, siehe # 5949. Obwohl dieses Thema geschlossen zu sein scheint, erinnere ich mich nicht, dass es eine klare Vereinbarung oder Schlussfolgerung gab.

Sobald wir zu Array-Ansichten wechseln, wird es einfacher, diese Richtungen zu erkunden. Wenn Sie slice(A, i, :) sagen, erhalten Sie insbesondere das gewünschte Verhalten. (Dies geschieht derzeit, jedoch auf Kosten der Einführung eines langsameren Typs, des SubArray.)

Aus rein mathematischer Sicht ergeben sich alle hier vorgestellten Probleme aus einer Verschmelzung (und Verwechslung) dessen, was wir unter Arrays verstehen und was wir unter Vektoren / Tensoren / Matrizen verstehen. Arrays sind konzeptionell einfach Listen (oder im Fall von n-dim-Arrays Listen von Listen). Daher gibt es keine natürliche Spezifikation für Operationen wie Array-Multiplikation, Transposition usw. Während Funktionen wie Permutationen, elementweise Operationen und achsspezifische Operationen (Mittelwert, Median usw.) sinnvoll sind und in a eindeutig definiert werden können Auf natürliche Weise können Operationen wie Punktprodukte nicht durchgeführt werden.

Wie oben erwähnt, sind Vektoren und Tensoren geometrische Objekte, und obwohl es möglich ist, sie mithilfe von Arrays darzustellen, enthalten diese Darstellungen nicht den gleichen Strukturreichtum wie die mathematischen Objekte, die sie darstellen. Die Transponierung eines 1-Dim-Arrays ist ein No-Op. Die Transponierte eines Vektors ist sein Dual. Die Transponierung eines 2-Dim-Arrays kann eindeutig und natürlich als Permutation seiner Dimensionen definiert werden, dies gilt jedoch im Allgemeinen nicht für Tensoren: Während der natürliche Fall für Rang- (1,1) -Tensoren (auch bekannt als Matrizen) gilt, a Rang (2,0) Tensor transponiert in einen Rang (0,2) Tensor. Wiederum geht durch die Behandlung von Tensoren als Arrays die geometrische Information verloren, die Tensortensoren erzeugt.

Dies ist wichtig, wenn Vorgänge wie Punktprodukte definiert werden. Ein Punktprodukt hat eine spezifische geometrische Bedeutung (die Projektion eines Vektors auf den durch einen zweiten Vektor definierten Doppelraum), und daher erfordert eine konsistente Definition von Punktprodukten die Beibehaltung der in Vektoren enthaltenen geometrischen Informationen. Die Verwendung bestimmter Annahmen könnte es ermöglichen, Arrays zu verwenden und dennoch einen Großteil der Anwendungsfälle abzudecken. Diese Annahmen sind jedoch chaotisch (wie aus den verschiedenen Vorschlägen in diesem Thread hervorgeht) und erschweren die Arbeit für alle, die eine reichhaltigere Struktur von Tensoren benötigen .

Betrachten Sie dies als eine starke Stimme für den Vorschlag von thomasmcoffee, einen reichhaltigeren AbstractTensor-Typ aufzunehmen. Meine persönliche Präferenz wäre, dass Operationen wie Transposition und Punktprodukte nicht einmal für Arrays definiert wurden, aber da ich vermute, dass die meisten Leute diese Ansicht nicht teilen würden, würde ich zumindest die Fähigkeit wollen, echte Tensoren zu erzeugen, falls dies erforderlich sein sollte.

Die praktische Implikation dieser Perspektive scheint zu sein, dass Arrays mit einer Teilmenge von Tensoren identifiziert werden sollten und das Transponieren eines 1-d-Arrays ein DualVector oder vielleicht einen Fehler ergeben sollte. Meiner Ansicht nach ist dies analog zu Operationen mit reellen Zahlen, die komplexe Zahlen ergeben.

Meine Perspektive wäre, dass die allgemeine AbstractArray-Familie, ein (mehrdimensionaler) Datencontainer, ausreichend allgemein ist, um ein unverzichtbarer Bestandteil jeder technischen Programmiersprache zu sein. Ein Tensor, der strengen mathematischen Regeln folgt, obwohl er mir sehr am Herzen liegt, ist ein gutes Objekt für ein spezielles Paket. Tatsächlich arbeite ich an etwas in der von @jdbates in https://github.com/Jutho/TensorToolbox.jl angegebenen Richtung . Es ist bisher nicht dokumentiert und weitgehend ungetestet. Ich habe es für die Dinge geschrieben, die ich persönlich in der Quanten-Körperphysik brauche, aber ich hoffe, es ist so konstruiert, dass es allgemein genug und erweiterbar ist, um für die größere Gemeinschaft von Mathematikern und Physikern nützlich zu sein, die sich für die Arbeit mit Tensoren interessieren.

Um einige Details zu nennen (kopiert aus dem JuliaQuantum-Forum): Ich habe beschlossen, eine neue Typhierarchie für Tensoren zu definieren, die unabhängig vom AbstractArray-Typ von Julia ist (obwohl der grundlegende Tensor nur ein Wrapper für Array ist). Diese Typhierarchie soll etwas formeller funktionieren. Tensorindizes sind Vektorräumen zugeordnet (im Folgenden als Indexräume bezeichnet), und wenn sich die Art des Vektorraums, dem der Tensorindex zugeordnet ist, von seinem dualen unterscheidet, entspricht dies einem Tensor, der zwischen kovarianten und kontravarianten Indizes unterscheidet.

Der erste Teil des Pakets ist also der abstrakte Teil zum Definieren von Vektorräumen, in dem ich die Typhierarchie von Julia-Objekten an die mathematische Hierarchie von Vektorräumen anpasse. Ein allgemeiner Vektorraum V gibt es in vier Varianten, die der Darstellungstheorie der allgemeinen linearen Gruppe auf V entsprechen, dh V selbst (fundamentale Darstellung), konj (V), dual (V) und dual (konj (V)). Für reale Vektorräume ist konj (V) = V und es gibt nur V und dual (V), die kontravarianten und kovarianten Vektoren entsprechen. Dann gibt es die inneren Produkträume, und auf der obersten Ebene der Hierarchie befinden sich die euklidischen Räume, die innere Produkträume mit einem standardmäßigen euklidischen inneren Produkt (dh einer orthogonalen Basis) sind. In der Physik ist es auch nützlich, über Vektorräume nachzudenken, die in verschiedene Sektoren zerlegt sind, dh durch z. B. irreduzible Darstellungen von Symmetrieaktionen bewertet werden.

Tensoren sind Objekte, die in (einem Unterraum von) dem Tensorprodukt einiger elementarer Vektorräume leben. Abgesehen von dem Standard-Tensor, der ein Objekt ist, das im Tensorproduktraum seiner Indexräume lebt, könnte man jedoch Tensoren bauen, die beispielsweise im invarianten Sektor eines Tensorprodukts von durch Irreps abgestuften Räumen, dem symmetrischen oder antisymmetrischen Unterraum von, leben ein Tensorprodukt identischer Räume, ... Man könnte fermionische Vektorräume als Indexräume haben, was impliziert, dass eine Permutation der Tensorindizes bestimmte Vorzeichenänderungen in Abhängigkeit von den Paritätssektoren usw. hervorruft.

Dann sollen bestimmte Operationen für Tensoren definiert sein, von denen die wichtigste die Kontraktion von Tensoren ist, aber auch z. B. orthogonale Faktorisierungen (Singularwertzerlegung) usw. Schließlich sollten lineare Karten vorhanden sein, die einen Tensor auf einen anderen abbilden. Sie verdienen einen besonderen Typ, da man sie normalerweise nicht vollständig als Matrix codieren möchte, sondern auf eine Weise, dass das Matrixvektorprodukt für die Verwendung in iterativen Methoden (Lanczos usw.) effizient berechnet werden kann. Meine beiden bisher vorhandenen Pakete (TensorOperations.jl und LinearMaps.jl) implementieren diese Funktionalität für Standard-Arrays. Die im Aufbau befindliche Tensor-Toolbox würde sie für die neue AbstractTensor-Hierarchie überladen / neu definieren.

Ich hoffe, dass dieses Paket so allgemein gehalten ist, dass es auch für die breitere Physik / Mathematik-Community nützlich ist. Wenn beispielsweise jemand vorbeikommt, der ein Paket für die Arbeit mit Mannigfaltigkeiten erstellt, kann er einen TangentSpace-Vektorraum als Unterraum des abstrakten InnerProductSpace definieren und dann sofort Tensoren erstellen, die im Tensorprodukt einiger Tangenten- und Kotangensräume leben. Tatsächlich denke ich darüber nach, den Teil zum Definieren von Vektorräumen in ein separates Paket aufzuteilen, das zu einem Paket zum Definieren mathematischer Strukturen / Objekte werden könnte.

Schließlich kommt die Interaktion mit Standard-Julia vom Aufrufen von tensor auf einem Standard Array , wodurch es in ein Objekt vom Typ Tensor mit den Indizes verpackt wird, die Leerzeichen vom Typ CartesianSpace . Dies ist der reale Standardvektorraum R ^ n mit dem euklidischen Produkt, bei dem kein Unterschied zwischen dem kovarianten und dem kontravarianten Index besteht. Ich denke, das bedeutet am besten, was ein Standard-Julia-Array ist.

@ JeffBezanson , ich bin ambivalent in Bezug auf die Behandlung von Arrays als Teilmengen von Tensoren. Auf diese Weise gehen keine Informationen verloren, aber gleichzeitig gibt es mehrere mögliche Interpretationen für Arrays, und die Tensorinterpretation ist nicht immer (oder sogar normalerweise) sinnvoll. Betrachten Sie Bilder: Ein Bild kann als ein vektorwertiges Feld auf einer (typischerweise 2d) Mannigfaltigkeit betrachtet werden. Wenn Sie dieses Feld auf ein rechteckiges Raster beschränken, erhalten Sie eine Struktur, die Sie natürlich mithilfe eines 3D-Arrays darstellen möchten. Tatsächlich ist dies jedoch nur eine Abbildung aus dem Raum der Gitterpunkte in den Vektorraum {R, G, B}, sodass sich die geometrische Bedeutung der ersten beiden Dimensionen (die x- und y-Beschriftungen des Gitters) von der unterscheidet geometrische Bedeutung der dritten Dimension (die tatsächlich ein Vektor ist).

Ich bin nicht gegen @Juthos Vorschlag, die aufzuteilen . Er hat wahrscheinlich Recht, dass die Anzahl der Benutzer, die die vollständige Tensormechanik benötigen, viel geringer ist als die Anzahl der Personen, die nur einfache Array-Operationen wünschen. Die Frage, die wir hier wirklich zu stellen versuchen, lautet: "In welchen Bereich sollte die lineare Algebra fallen?"

Die Maschinerie der linearen Algebra ist eine ausreichend substanzielle Teilmenge der Maschinerie der Tensoralgebra, so dass es meines Erachtens zumindest keinen Sinn macht, die erstere zu implementieren, ohne auch die letztere zu implementieren. Operationen wie v'M werden präziser und konsistenter dargestellt, wenn wir eine Vorstellung von co- und kontravarianten Vektoren haben, aber das bringt uns bereits den größten Weg zu allgemeinen Tensoroperationen.

Ich stimme Ihnen zu, dass dies konzeptionell Operationen mit reellen Zahlen ähnelt, die komplexe Zahlen zurückgeben.

Betrachten Sie Bilder: Ein Bild kann als ein vektorwertiges Feld auf einer (typischerweise 2d) Mannigfaltigkeit betrachtet werden. Wenn Sie dieses Feld auf ein rechteckiges Raster beschränken, erhalten Sie eine Struktur, die Sie natürlich mithilfe eines 3D-Arrays darstellen möchten. Tatsächlich ist dies jedoch nur eine Abbildung aus dem Raum der Gitterpunkte in den Vektorraum {R, G, B}, sodass sich die geometrische Bedeutung der ersten beiden Dimensionen (die x- und y-Beschriftungen des Gitters) von der unterscheidet geometrische Bedeutung der dritten Dimension (die tatsächlich ein Vektor ist).

Während dies Ihre allgemeine Nachricht nicht adressiert oder beeinträchtigt, arbeitet https://github.com/timholy/Images.jl/pull/135 an einer Implementierung dieser Idee für Bilder. Ich hoffe, dass dies auch den Umgang mit Farbstrukturtensoren erleichtert, die ich für ein Projekt verwenden möchte.

Am 23. August 2014, um 20:36 Uhr, schrieb jdbates [email protected] :

@ JeffBezanson , ich bin ambivalent in Bezug auf die Behandlung von Arrays als Teilmengen von Tensoren. Auf diese Weise gehen keine Informationen verloren, aber gleichzeitig gibt es mehrere mögliche Interpretationen für Bilder, und die Tensorinterpretation ist nicht immer (oder sogar normalerweise) sinnvoll. Betrachten Sie Bilder: Ein Bild kann als ein vektorwertiges Feld auf einer (typischerweise 2d) Mannigfaltigkeit betrachtet werden. Wenn Sie dieses Feld auf ein rechteckiges Raster beschränken, erhalten Sie eine Struktur, die Sie natürlich mithilfe eines 3D-Arrays darstellen möchten. Tatsächlich ist dies jedoch nur eine Abbildung aus dem Raum der Gitterpunkte in den Vektorraum {R, G, B}, sodass sich die geometrische Bedeutung der ersten beiden Dimensionen (die x- und y-Beschriftungen des Gitters) von der unterscheidet geometrische Bedeutung der dritten Dimension (die tatsächlich ein Vektor ist).

Ich bin damit einverstanden, dass Tensoren Arrays nicht ersetzen. Dieses obige Beispiel ist in der Tat eine andere mathematische Struktur (dh ein Vektorbündel oder allgemeiner ein Tensorbündel), deren Darstellung auch als mehrdimensionales Array angegeben werden kann, indem ein Gitter für die Mannigfaltigkeitskoordinaten und eine Basis für den Vektorraumteil ausgewählt wird. In der Tat können Sie verschiedene mathematische Objekte / Strukturen haben, die auf koordinatenunabhängige / basenunabhängige Weise gut definiert sind, aber (nach Auswahl eines Koordinatensystems oder einer Basis) als mehrdimensionales Array dargestellt werden können. Mehrdimensionale Arrays sind also sicherlich nicht auf die Darstellung von Tensoren beschränkt. Der umgekehrte Weg schlägt ebenfalls fehl, da nicht alle Tensoren eine bequeme Darstellung unter Verwendung eines mehrdimensionalen Arrays haben. Dies ist nur dann der Fall, wenn Sie eine bestimmte Basis verwenden, die als Produktbasis bezeichnet wird. Diese wird erhalten, indem das direkte Produkt aller möglichen Kombinationen der einzelnen Basisvektoren der im Tensorproduktraum beteiligten Vektorräume verwendet wird. In einigen Fällen, z. B. wenn Tensoren in einem symmetrieinvarianten Unterraum des Tensorproduktraums verwendet werden, ist eine solche Darstellung nicht mehr möglich, und Sie müssen eine andere Basis für den gesamten Raum definieren, in Bezug auf die der Tensor gerade dargestellt wird eine lange eindimensionale Liste von Zahlen.

Ich bin nicht gegen @Juthos Vorschlag, die aufzuteilen . Er hat wahrscheinlich Recht, dass die Anzahl der Benutzer, die die vollständige Tensormechanik benötigen, viel geringer ist als die Anzahl der Personen, die nur einfache Array-Operationen wünschen. Die Frage, die wir hier wirklich zu stellen versuchen, lautet: "In welchen Bereich sollte die lineare Algebra fallen?"

Die Maschinerie der linearen Algebra ist eine ausreichend substanzielle Teilmenge der Maschinerie der Tensoralgebra, so dass es meines Erachtens zumindest keinen Sinn macht, die erstere zu implementieren, ohne auch die letztere zu implementieren. Operationen wie v'M werden präziser und konsistenter dargestellt, wenn wir eine Vorstellung von co- und kontravarianten Vektoren haben, aber das bringt uns bereits den größten Weg zu allgemeinen Tensoroperationen.

Ich stimme Ihnen zu, dass dies konzeptionell Operationen mit reellen Zahlen ähnelt, die komplexe Zahlen zurückgeben.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an.

Es gibt mehrere mögliche Interpretationen für Arrays, und die Tensorinterpretation ist nicht immer (oder sogar normalerweise) sinnvoll. Betrachten Sie Bilder: Ein Bild kann als ein vektorwertiges Feld auf einer (typischerweise 2d) Mannigfaltigkeit betrachtet werden. Wenn Sie dieses Feld auf ein rechteckiges Raster beschränken, erhalten Sie eine Struktur, die Sie natürlich mithilfe eines 3D-Arrays darstellen möchten. Tatsächlich ist dies jedoch nur eine Abbildung aus dem Raum der Gitterpunkte in den Vektorraum {R, G, B}, sodass sich die geometrische Bedeutung der ersten beiden Dimensionen (die x- und y-Beschriftungen des Gitters) von der unterscheidet geometrische Bedeutung der dritten Dimension (die tatsächlich ein Vektor ist).

Es war genau diese Art von Unterscheidung, die ich im fiktiven AbstractTensorArray -Vorschlag von https://github.com/JuliaLang/julia/issues/4774#issuecomment -38333295 zu erfassen versuchte, indem ich sowohl Array-like als auch Tensor zuließ -ähnliche Abmessungen. Nach diesem Schema würde ich erwarten, Ihr Beispiel als darzustellen

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

so dass die x-, y- und RGB-Dimensionen "unten", "oben" bzw. "neutral" sind. Geometrische Operationen (z. B. affine Transformationen) könnten dann die Gitterkoordinatendimensionen tensorartig behandeln, während die RGB-Werte Array-artig abgebildet werden. (Wenn Sie die RGB-Werte später geometrisch behandeln möchten, müssen Sie die Maske zu diesem Zweck explizit ändern, aber ich würde vermuten, dass (a) es weniger häufig ist, dass zwei verschiedene Arten von geometrischen Operationen auf verschiedene Teilräume von angewendet werden die gleiche Datentabelle, und (b) in dieser Situation würde eine explizite Konvertierung wahrscheinlich die Klarheit des Codes verbessern.)

Ich hatte die konjugierten Darstellungen, die @Jutho erwähnt, nicht berücksichtigt, aber es scheint mir, dass diese Verallgemeinerung behandelt werden könnte, indem der gleiche Maskierungsansatz für komplexe Räume weiter ausgebaut wird.

Die Frage, die wir hier wirklich zu stellen versuchen, lautet: "In welchen Bereich sollte die lineare Algebra fallen?"

Sobald ein Entwurf festgelegt ist, wie Array- und Tensoroperationen zusammenspielen, können die Entitäten für die lineare Algebra nur durch Sonderfälle (wie die oben verwendeten Aliase) definiert werden, sodass der Benutzer der reinen linearen Algebra dies nicht bemerken kann die gesamte verallgemeinerte Tensorhierarchie, bis sie benötigt wird (muss aber nicht neu geschrieben werden, wenn und wann dies der Fall ist). Ich würde also kein Problem sehen (außer vielleicht aufblähen), das Ganze in Base zu bringen.

so dass die x-, y- und RGB-Dimensionen "unten", "oben" bzw. "neutral" sind. Geometrische Operationen (z. B. affine Transformationen) könnten dann die Gitterkoordinatendimensionen tensorartig behandeln, während die RGB-Werte Array-artig abgebildet werden. (Wenn Sie später die RGB-Werte geometrisch behandeln möchten, müssen Sie die Maske zu diesem Zweck explizit ändern, aber ich würde vermuten, dass (a) es weniger häufig ist, dass zwei verschiedene Arten von geometrischen Operationen auf verschiedene Teilräume von angewendet werden dieselbe Datentabelle und (b) in dieser Situation würde eine explizite Konvertierung wahrscheinlich die Klarheit des Codes verbessern.)

Ich denke, Sie mischen hier etwas. In der obigen Diskussion wurde tatsächlich erklärt, dass die x- und y-Koordinaten nicht die Vektorrauminterpretation trugen, da sie Koordinaten auf einer willkürlich gekrümmten Mannigfaltigkeit entsprechen können, nicht notwendigerweise einem flachen Raum. Es war die RGB-Dimension, die die Vektorinterpretation erhielt, obwohl dies möglicherweise auch nicht die beste Wahl ist, da ich mich zu erinnern scheine (ich habe keinen anständigen Hintergrund in der Bildverarbeitung), dass der Farbraum auch ziemlich gekrümmt ist. Auch für den Fall, dass die Domäne (x und y) einen Vektorraum bildet, warum sollten x und y auf und ab sein, oder war dies nur ein Beispiel für Ihre Notation?

Wie auch immer, ich habe auch mit TensorToolbox.jl begonnen, indem ich kovariante und kontravariante Indizes als eine Art Parameter oder Maske bezeichnet habe, aber dies wurde bald zu einem völligen Albtraum, weshalb ich zu einer Darstellung gewechselt bin, bei der jeder Tensor ein Element eines Vektorraums ist und um Operationen auszuführen, muss überprüft werden, ob Leerzeichen übereinstimmen, genau wie Sie überprüfen müssen, ob Größen übereinstimmen, wenn Sie Operationen mit Arrays ausführen.

Die x- und y-Koordinaten trugen nicht die Interpretation des Vektorraums

Entschuldigung, ich habe "rechteckiges Gitter" überlesen --- Ich denke,

Jeder Tensor ist ein Element eines Vektorraums

Scheint eine nette Idee zu sein --- Ich würde gerne einige Beispiele sehen, wie es für den Benutzer funktioniert (ich bin nicht weit gekommen, den Code zu lesen).

Ich habe einen neuen Vorschlag für dieses Problem.


(1) Schneiden nach APL-Art.

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

Dies bedeutet insbesondere, dass "Singleton-Slices" - dh Slices, bei denen der Index skalar oder nulldimensional ist - immer gelöscht werden und M[1,:] und M[:,1] beide Vektoren sind und nicht einer ein Vektor während die andere eine Zeilenmatrix oder eine andere solche Unterscheidung ist.


(2) Führen Sie die Wrapper-Typen Transpose und ConjTranspose für Vektoren und Matrizen ein. Mit anderen Worten, so etwas:

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)

und alle geeigneten Methoden, damit diese für Vektoren und Matrizen so funktionieren, wie sie sollten. Wir möchten es vielleicht darauf beschränken, nur für Vektoren und Matrizen zu arbeiten, da unklar ist, was eine allgemeine Transponierung für beliebige Dimensionen bedeuten sollte (obwohl nur das Umkehren von Dimensionen verlockend ist). Wenn Sie a' schreiben, erhalten Sie ConjTranspose(a) und ebenfalls v.' produziert Transpose(a) .


(3) Definieren Sie verschiedene spezialisierte Methoden für (konjugierte) transponierte Vektoren und Matrizen, wie z.

*(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) ]

usw., einschließlich des Ersetzens aller schrecklichen At_mul_B -Funktionen und des speziellen Parsens durch eine faule (konjugierte) Transponierungskonstruktion, gefolgt vom Versand der Typen Transpose und ConjTranspose .


(4) Beschränken Sie Rundfunkvorgänge auf Fälle, in denen die Argumente Skalare oder Arrays mit derselben Anzahl von Dimensionen sind. Daher schlägt Folgendes fehl, das derzeit wie gezeigt funktioniert:

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

Stattdessen müssen Sie Folgendes tun:

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

Ich glaube, dieser Vorschlag löst alle wichtigen Probleme, die wir derzeit haben:

  1. Symmetrisches Schneideverhalten - Nachlaufabmessungen sind keine Besonderheiten mehr.
  2. v'' === v .
  3. v' == v .
  4. v'w ist das Punktprodukt von v und w - insbesondere ist es ein Skalar, kein Ein-Element-Vektor.
  5. v*w' ist das äußere Produkt von v und w .
  6. M*v ist ein Vektor.
  7. M*v' ist ein Fehler.
  8. v'*M ist ein transponierter Vektor.
  9. v*M ist ein Fehler.
  10. At_mul_B Operatoren und spezielle Analyse verschwinden.

: +1: zu allem. Ich habe in # 6837 an 2 und 3 gearbeitet, es aber nie beendet. @ Simonbyrne hat sich auch

+1 auch. Klingt so, als würde es überall ein ziemlich konsistentes Verhalten bieten.

Der einzige wirklich störende Teil dieses Vorschlags wäre tatsächlich, dass M[1,:] ein implizit vertikaler Vektor und keine explizit horizontale Zeilenmatrix ist. Ansonsten ist es tatsächlich eine ziemlich reibungslose, unterbrechungsfreie Reihe von Änderungen (man hofft). Die wichtigste Offenbarung (für mich) war, dass das APL-Schneideverhalten mit faulen Transponierungen kombiniert werden konnte. Wenn wir uns einkaufen, können wir einen Plan ausarbeiten und die Arbeit aufteilen. Ich hoffe wirklich, dass faule Transponierungen und inszenierte Funktionen eine gewisse Reduzierung und Vereinfachung des Codes ermöglichen.

Ja bitte! Die Tensor-Transponierung sollte wahrscheinlich jede benutzerdefinierte Permutation zulassen, wobei die Dims standardmäßig umgekehrt werden.

Die Tensor-Transponierung sollte wahrscheinlich jede benutzerdefinierte Permutation zulassen, wobei die Dims standardmäßig umgekehrt werden.

Das scheint den Typ ein wenig zu komplizieren, vielleicht können wir einen PermuteDims -Typ haben, der eine beliebige Permutation fauler Dimensionen ermöglicht.

@Stefan : Dies scheint eine ziemlich gute Idee zu sein, um den Vektor und 2-dim zu
Algebra. Nur ein paar Herausforderungen:

  1. In Bezug auf Array-Fälle mit mehreren Dimensionen: Für ein Array A mit Dimension
    (i_1, i_2, ..., i_n), wenn man möchte, dass die Transponierte auf [i_2, i_3] angewendet wird
    Dimensionen - oder sogar Hash auf den Dimensionen [i_2, i_4]. Schaffst du das?
    die neue Definition von transponieren?
  2. In Bezug auf die Singleton-Dimension: Es ist möglich, dass es sich um ein Singleton-Slice handelt
    absichtlich verlassen. Sollte Julia diese Singleton-Dimension nach dem behalten
    Berechnung? Zum Beispiel, wenn man einen Vektor als Array V in der definiert
    Dimension von (2,1) und möchte die Transponierte mit einer Matrix A in multiplizieren
    Dimension (2,3,4). Können Sie das Ergebnis von v '* A in der Dimension von ergeben?
    (1,3,4)?

Am Do, 16. Oktober 2014, um 14:31 Uhr, Stefan Karpinski [email protected]
schrieb:

Die einzige Störung wäre tatsächlich, dass M [1,:] ein (vertikaler) Vektor ist
eher als eine Zeilenmatrix. Ansonsten ist es eigentlich ziemlich glatt,
unterbrechungsfreie Änderungen (man hofft). Die wichtigste Offenbarung (für mich) war
Dieses APL-Slicing-Verhalten könnte mit faulen Transponierungen kombiniert werden. Wenn wir bekommen
Buy-In können wir einen Plan ausarbeiten und die Arbeit aufteilen. ich hoffe wirklich
dass faule Transponierungen und inszenierte Funktionen eine gewisse Code-Reduzierung ermöglichen und
Vereinfachung.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/JuliaLang/julia/issues/4774#issuecomment -59425385.

Zu 2 & 3: Nachdem ich einen groben Stich gemacht hatte, kam ich zu dem Schluss, dass die Vektortransponierung KEIN Subtyp von AbstractVector , sonst wird alles viel zu chaotisch (siehe Diskussion auf # 6837). Ich denke, der vernünftigste Weg nach vorne ist mit Transpose{T,A} <: AbstractMatrix{T} und einem separaten Covector -Typ (+ Conjugate Varianten).

Das andere wichtige Problem, auf das ich gestoßen bin, ist, dass Sie häufig einen bestimmten Matrixtyp, seine Transponierte oder seine konjugierte Transponierte versenden möchten. Leider konnte ich keine Möglichkeit finden, dies über vorhandene Maschinen auszudrücken (siehe diese Mailinglistendiskussion ). Ohne dies befürchte ich, dass wir über 3x3 mögliche Kombinationen von Argumenten eine Menge @eval -ing bekommen werden.

@simonbyrne , ich verweise auf Ihre Erfahrung mit der Implementierung. Scheint der Rest vernünftig?

Ich habe darauf hingewiesen (in weniger öffentlichen Foren, daher wird hier wahrscheinlich kurz darauf hingewiesen), dass eine mögliche Alternative darin besteht, die gesamte Formgebung intern zu handhaben, indem die Arten von Indizes erweitert werden, die SubArrays verwenden können. Insbesondere könnte man einen Typ "transponierter Bereich" haben, der dem SubArray eine transponierte Form verleiht, selbst wenn das übergeordnete Array ein Vector . (Siehe https://github.com/JuliaLang/julia/blob/d4cab1dd127a6e13deae5652872365653a5f4010/base/subarray.jl#L5-L9, wenn Sie nicht mit der Implementierung von SubArrays vertraut sind.)

Ich bin nicht sicher, ob diese alternative Strategie das Leben leichter oder schwerer macht. Es reduziert die Anzahl der nach außen gerichteten Typen, was bedeuten kann, dass weniger Methoden benötigt werden. (Als jemand, der aufgrund des Color -Übergangs in Images fehlende Methoden ausfüllt, scheint dies eine gute Sache zu sein.) Auf der anderen Seite könnte dies ohne einen bequemen dreieckigen Versand möglich sein Machen Sie es etwas umständlicher, selektive Methoden zu schreiben, was die von @simonbyrne aufgeworfenen Probleme verschlimmern könnte.

Einblicke wären sehr willkommen.

Abgesehen von solchen Details gefällt mir die Form des Vorschlags von @StefanKarpinski . Ich bin nicht an eine Indizierung im APL-Stil gebunden, aber insgesamt vermute ich, dass dies eine bessere Wahl ist als die von Matlab abgeleiteten Regeln, die wir jetzt haben.

Zwei Gedanken:

  • Wenn eine Indizierung wie A[[2], :] idiomatisch wird, erscheint es etwas verschwenderisch, einen Vektor erstellen zu müssen, um nur den einzelnen Index 2 zu umschließen. Sollten wir in Betracht ziehen, A[(2,), :] für dasselbe oder ähnliches zuzulassen? Ich denke, ein einzelner Elementbereich ist in Ordnung, aber es wäre schön, eine Syntax dafür zu haben, die fast so praktisch ist wie [2] .
  • Wenn für die Übertragung eine übereinstimmende Anzahl von Dimensionen erforderlich sein sollte, sollte es eine einfache Möglichkeit geben, einem Array Singleton-Dimensionen hinzuzufügen, möglicherweise so etwas wie die newaxis -Indizierung von numpy.

Ich dachte daran, vorzuschlagen, dass die Indizierung mit Semikolons, a la A[2;:] , ein anderer Indizierungsmodus sein könnte, bei dem das Ergebnis immer die gleiche Anzahl von Dimensionen wie A - dh keine Singletons fallen lassen und mit indizieren Alles, was einen Rang von mehr als eins hat, ist ein Fehler. Beschlossen, dies der Einfachheit halber aus dem Kernvorschlag herauszulassen, aber so etwas scheint eine gute Sache zu sein.

Ich kann die Bedenken von @simonbyrne sehen . Im Prinzip ist ein Covektor jedoch auch nur ein Vektor, der in einem anderen Vektorraum lebt, nämlich dem dualen Raum. Es ist also auch etwas unangenehm, den Typ Transpose oder Covector nicht zu einem Subtyp von AbstractArray machen. Eine mögliche Lösung, die eine wesentliche Änderung darstellen würde und wahrscheinlich nicht in Betracht gezogen wird (aber ich wollte sie trotzdem erwähnen), besteht darin, der gesamten AbstractArray -Familie einen zusätzlichen Typparameter trans , die Werte :N , :T oder :C haben könnten. Für alle Methoden, bei denen nur angenommen wird, dass ein Vektor eine eindimensionale Liste von Zahlen ist, müssten sie nicht zwischen verschiedenen Werten dieses endgültigen Parameters unterscheiden, sodass die entsprechenden Methodendefinitionen unverändert bleiben können.

Für N-dimensionale Arrays mit N> 2 gibt es verschiedene Optionen. Entweder gibt transpose einen Fehler aus und es ist unmöglich, tatsächlich ein Objekt vom Typ AbstractArray{3,Float64,trans} zu erstellen, wobei trans!=:N oder alternativ :T nur Zeilenmajor bedeutet und transpose eines allgemeinen Arrays bewirken, dass alle Dimensionen umgekehrt werden. Ich denke, letzteres ist auch die akzeptierte Konvention derjenigen, die die grafische Notation von Penrose verwenden (siehe http://en.wikipedia.org/wiki/Penrose_graphical_notation, obwohl die Transponierung dort nicht erklärt wird, aber auch das zitierte Buch von Cvitanović).

Ich sehe nicht wirklich, dass die Rolle für beliebige Indexpermutationen von transpose , dafür gibt es permutedims und vielleicht einen faulen Ansatz mit überarbeiteten SubArrays. Darüber hinaus besteht die Hauptmotivation für dieses Problem darin, den A_mul_B-Zoo zu vereinfachen, und Tensorkontraktionen höherer Ordnung werden (und sollten) ohnehin nicht durch normale Multiplikation unterstützt.

Ich bin sicher, dass es im Zusammenhang mit diesem Ansatz einige neue Probleme gibt, an die ich noch nicht gedacht habe.

Ich denke, ich habe hier eine vernünftige Lösung für das Versandproblem gefunden .

Der Vorschlag von

@toivoh ,

  • A[2:2,:] behält auch die Dimension bei, und dies erfordert keine Zuordnung oder neue Syntax.
  • So etwas wie newaxis scheint hervorragend machbar. In der Tat scheint es mit der Architektur in # 8501 möglich zu sein, Broadcasting direkt durch Indizierung zu erstellen: Sie haben einen Indextyp, der immer in 1 aufgelöst wird, unabhängig davon, welchen Wert der Benutzer in diesen Steckplatz einfügt.

Das Problem mit 2:2 ist die Wiederholung, wenn der Index einen langen Ausdruck anstelle von nur 2 . Natürlich können Sie jederzeit Ihre eigene Funktion definieren, um aus einem Index einen Bereich zu erstellen.

Sehr guter Vorschlag: +1:.

Erinnern Sie mich daran, warum wir v' == v ?

Wir brauchen das nicht wirklich, aber es ist irgendwie nett, da das Dual eines (endlichdimensionalen) Vektorraums dazu isomorph ist.

Oder stärker gesagt, da Julias Arrays nicht zwischen kovarianten und kontravarianten Indizes unterscheiden, ist es nur sinnvoll, sich dies als Vektoren in einem kartesischen Raum (euklidische Metrik = Identitätsmatrix = Kronecker-Delta) vorzustellen, in dem der duale Raum natürlich ist isomorph.

Ich bin mir nicht sicher, ob wir v '== v wollen, aber ich denke, das ist ziemlich orthogonal zu
der Rest. Wollen wir, dass eine Spaltenmatrix und ein Vektor gleich sind, wenn sie gleich sind?
gleiche Elemente haben?

Das ist eigentlich ein anderes Problem, da sie unterschiedliche Anzahlen von Dimensionen haben.

Insbesondere wird durch diesen Vorschlag die Identifikation zwischen einem Vektor und einer Spaltenmatrix effektiv entfernt. Wenn Sie eine Matrix horizontal oder vertikal schneiden, erhalten Sie einen Vektor in beide Richtungen. Früher konnte man nachfolgende Singleton-Dimensionen ignorieren - oder so tun, als gäbe es mehr als tatsächlich. Es ist keine gute Idee mehr, dies zu tun, da ein Vektor aus einem beliebigen Slice eines Arrays stammen kann.

Wäre es sinnvoll zu convert etwas von 1-d bis 2-d durch eine nachgestellten Singleton Dimension hinzuzufügen?

Mit diesem Vorschlag halte ich das möglicherweise nicht mehr für eine gute Idee. Aber vielleicht verhalten sich Vektoren immer noch wie Spalten, während sich Covektoren wie Zeilen verhalten.

Eine Sache, die ich in # 8416 bemerkt habe, ist, dass sparsevec momentan als einspaltige CSC-Matrix unordentlich gefälscht ist. Sparse sollte in der Lage sein, ziemlich gut in dieses zu passen, sobald ein geeigneter 1-d-Sparse-Vektortyp implementiert ist (was als der einfachste nützliche Fall eines generischen Nd-COO-Typs herausfallen würde, muss nur geschrieben werden).

Nehmen Sie das alles einfach auf. Also würde das Folgende nicht funktionieren?

A [1,:] * A * A [:, 1] # Zeile aus einer Matrix * Matrix * Spalte aus einer Matrix ???

Sie schrieben

v'w ist das Punktprodukt von v und w - insbesondere ist es ein Skalar, kein Ein-Element-Vektor.

Auch v '* w ist ein Skalar?

Ich mag die Idee, dass Punkt (x, y) zwei beliebige Elemente mit den Formen (1, ..., 1, m, 1, ..., 1) und nimmt
Rückgabe des Punktprodukts, egal was passiert. Aber ich möchte nicht, dass x * y in diesem Sinne einen Punkt (x, y) gibt
es sei denn, x ist ein Covektor und y ist ein Vektor.

Ich bin mir nicht sicher, ob dies eine so heiße Idee ist, aber vielleicht wäre es in Ordnung, wenn
A [:, 1,1] war ein Vektor und A [1,:, 1] oder A [:, 1,:] waren Covektoren.
Fühlt sich besser an, entlang einer Dimension für den Vektor zu fahren - dem Schlitz, auf dem Sie sich befinden
dürfen den Tensor kontrahieren, wobei die lineare Standardalgebra ist
1 (Zeilenvektoren) und 2 Spaltenvektoren.

Meiner Ansicht nach waren die beiden größten Herausforderungen, die wir zuvor in dieser Ausgabe angesprochen hatten:

(A) wie man Tensorsemantik (für Kontraktionen) und Array-Semantik (für Rundfunk) unterscheidet, wenn mit mehrdimensionalen Daten gearbeitet wird;
(B) wie man offensichtliche und bequeme lineare Algebra in einen konsistenten Rahmen einbettet, der sich auf höhere Dimensionen verallgemeinert.

Mir ist nicht klar, wie dieser Vorschlag mit einem dieser Probleme umgeht. Soweit ich das beurteilen kann, erfordert das Erreichen von (A) immer noch Ad-hoc-Benutzermanipulationen (wie bei der heutigen Funktionalität). und um (B) mit Lazy Wrappern zu adressieren, wären so etwas wie die von @timholy vorgeschlagenen SubArray-Erweiterungen erforderlich . An diesem Punkt wird es zu einer Lazy-Version des vor einiger Zeit diskutierten Maskierungsansatzes. Ich kann mir vorstellen, zusätzliche Unterstützung für (A) mit einem ähnlichen faulen Mechanismus (wie einem Wrapper-Typ List bereitzustellen, aber in all diesen Fällen scheint mir Faulheit eine optionale Strategie zu sein.

Ich weiß nicht, wie viele die Ansicht von @Jutho teilen, dass "Tensorkontraktionen höherer Ordnung ohnehin nicht durch normale Multiplikation unterstützt werden (und sollten)", aber ich könnte nicht mehr widersprechen: Ich mache nur das, was ich für gewöhnliches Engineering halte Mathe, und ich brauche sie die ganze Zeit. Während aktuelle Sprachen wie Mathematica und NumPy diesbezüglich ihre Designbeschränkungen haben (wie ich oben besprochen habe), werden sie zumindest unterstützt! Sobald Sie beispielsweise den Gradienten eines Vektorfeldes in einer einfachen numerischen Methode verwenden möchten, benötigen Sie Tensorkontraktionen höherer Ordnung.

Wenn Sie sagen: "... haben diesbezüglich ihre Designbeschränkungen (wie ich oben besprochen habe), werden sie zumindest unterstützt", sprechen Sie über fehlende Funktionen oder etwas Grundlegendes über Vektoren und Transponierungen, das nicht angesprochen werden kann eine höhere Ebene oder durch Hinzufügen von Funktionen?

Hat irgendetwas an diesem Vorschlag einen Konflikt mit der Verbesserung Ihrer Punkte (A) und (B)?

Ich sehe wirklich nicht, wie Tensorkontraktionen vom Standard-Multiplikationsoperator * von matlabs * oder von einer anderen eingebauten Matlab-Funktion unterstützt werden. Numpy hat eine eingebaute Funktion (ich habe den Namen vergessen), aber es ist auch ziemlich begrenzt, soweit ich mich erinnere.

Auch ich brauche ständig Tensorkontraktionen in ihrer allgemeinsten Form. Genau deshalb weiß ich, dass es nicht ganz einfach ist, die allgemeinste Tensorkontraktion anzugeben, geschweige denn effizient zu implementieren. Aus diesem Grund habe ich argumentiert, dass es dafür spezielle Funktionen geben muss, anstatt zu versuchen, einige halb funktionierende oder eher spezifische Funktionen in die Standardoperatoren in Julia Base zu packen, die nicht die Hälfte der Anwendungsfälle abdecken. Aber ich ändere gerne meine Meinung, zB wenn es eine "Standard" -Kontraktion gibt, die so viel wichtiger / nützlicher ist als jede andere? Dies kann jedoch sehr domänenabhängig sein und ist daher als allgemeine Regel für die Übernahme in Julia Base nicht geeignet.

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

Meiner Ansicht nach waren die beiden größten Herausforderungen, die wir zuvor in dieser Ausgabe angesprochen hatten:

(A) wie man Tensorsemantik (für Kontraktionen) und Array-Semantik (für Rundfunk) unterscheidet, wenn mit mehrdimensionalen Daten gearbeitet wird;
(B) wie man offensichtliche und bequeme lineare Algebra in einen konsistenten Rahmen einbettet, der sich auf höhere Dimensionen verallgemeinert.

Mir ist nicht klar, wie dieser Vorschlag mit einem dieser Probleme umgeht. Soweit ich das beurteilen kann, erfordert das Erreichen von (A) immer noch Ad-hoc-Benutzermanipulationen (wie bei der heutigen Funktionalität). und um (B) mit Lazy Wrappern zu adressieren, wären so etwas wie die von @timholy vorgeschlagenen SubArray-Erweiterungen erforderlich . An diesem Punkt wird es zu einer Lazy-Version des vor einiger Zeit diskutierten Maskierungsansatzes. Ich kann mir vorstellen, (A) mit einem ähnlichen Lazy-Mechanismus (wie einem List-Wrapper-Typ) zusätzlich zu unterstützen, aber in all diesen Fällen scheint mir Faulheit eine optionale Strategie zu sein.

Ich weiß nicht, wie viele die Ansicht von @Jutho teilen, dass "Tensorkontraktionen höherer Ordnung ohnehin nicht durch normale Multiplikation unterstützt werden (und sollten)", aber ich könnte nicht mehr widersprechen: Ich mache nur das, was ich für gewöhnliches Engineering halte Mathe, und ich brauche sie die ganze Zeit. Während aktuelle Sprachen wie Mathematica und NumPy diesbezüglich ihre Designbeschränkungen haben (wie ich oben besprochen habe), werden sie zumindest unterstützt! Sobald Sie beispielsweise den Gradienten eines Vektorfeldes in einer einfachen numerischen Methode verwenden möchten, benötigen Sie Tensorkontraktionen höherer Ordnung.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an.

Hier ist eine Kontraktion über den letzten Index von A und den ersten Index von B.
irgendwie wie der Punkt von 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

Ich war immer in der Lage, allgemeine Kontraktionen mit Umformen, Permutationen und vielleicht Komplexen durchzuführen
Konjugate bei Bedarf mehr oder weniger wie oben

Ich bin mir nicht sicher, was das große Problem mit Tensoren wirklich ist. Warum können wir nicht einfach einige davon implementieren?
Funktionen?

Ja genau. In dieser Ausgabe möchten wir uns nur darauf einigen, vorwärts zu kommen

  1. Welche Dimensionen müssen bei der Indizierung berücksichtigt werden? "APL-Stil" scheint unumstritten.
  2. Was gibt vector' ?

Für Tensorkontraktionen mit geeigneten Typen und abgestuften Funktionen könnten wir meiner Meinung nach tatsächlich ziemlich leistungsstarke Implementierungen erhalten.

Mein Gefühl ist, dass Tensoren auf sich selbst aufpassen und wir müssen sicher sein
dass Benutzer der linearen Algebra nicht frustriert werden.

Meine größte Sorge ist das

(Nehmen Sie eine Zeile aus einem 2d-Array) * (2d-Array) * (Nehmen Sie eine Spalte aus einem 2d-Array)

Das ist eine übliche Operation, die immer noch nicht funktioniert, wenn wir nicht nehmen
(eine Reihe nehmen) mit Covector oder vielleicht noch besser
Kennzeichnen Sie es mit einem allgemeinen Slot-Index.

@ JeffBezanson , wenn ich sage, dass diese Operationen unterstützt werden, meine ich, dass die integrierten Datentypen und Funktionen speziell für sie entwickelt wurden, beispielsweise wie die Dot -Funktion von Mathematica. Für den Benutzer gibt es daher eine integrierte, dokumentierte und / oder offensichtliche Möglichkeit, bestimmte Dinge zu tun. Für jedes Design ist es möglich, Unterstützung für alles zu erhalten, indem Funktionen hinzugefügt werden, genau wie bei der aktuellen Implementierung. Es geht also nicht um technische Konflikte, sondern um Design.

@Jutho , ich benutze MATLAB nicht viel, daher kann ich keinen Kommentar vector' zu tun ist.

A [1,:] * A * A [:, 1] # Zeile aus einer Matrix * Matrix * Spalte aus einer Matrix ???

Richtig - Sie müssten A[1,:]' * A * A[:,1] schreiben.

Auch v '* w ist ein Skalar?

Ja, v'w sind dasselbe. Eines der guten Dinge an diesem Vorschlag ist, dass billige syntaktische Hacks vollständig eliminiert werden.

Ich bin mir nicht sicher, ob das eine so heiße Idee ist ...

Ich glaube nicht. Eines der Ziele dieses Vorschlags ist es, die Regeln für das Schneiden und Indizieren symmetrisch zu gestalten. Dies würde einen der Indizes zu etwas Besonderem machen, was meines Erachtens den gesamten Zweck zunichte macht. Wenn das Schneiden asymmetrisch sein soll, können wir das aktuelle Verhalten beibehalten.

@thomasmcoffee Du musst nur genauer sein. Natürlich möchte jeder, dass die Dinge kohärent, dokumentiert, offensichtlich usw. sind. Die Frage ist, ob der Vorschlag auf dem Tisch diesen Zielen dient. Vielleicht wirkt sich der aktuelle Vorschlag überhaupt nicht auf diese Ziele aus, was in Ordnung ist. Solange er anderswo zu einer Verbesserung führt, haben wir immer noch eine Nettoverbesserung.

Lass mich das mal klarstellen

Wenn A nicht quadratisch ist

| | Aktuell | Vorgeschlagen | MATLAB |
| --- | --- | --- | --- |
| A * A [1,:] | Nein | Ja | Nein |
| A * A [1,:] '| Ja | Nein | Ja |
| A [:, 1] A | Nein |
A [:, 1] ' A | Ja | Ja | Ja |

und wenn A quadratisch ist

| | Aktuell | Vorgeschlagen | MATLAB |
| --- | --- | --- | --- |
| A * A [:, 1] | Ja | Ja | Ja |
| A * A [:, 1] '| Nein | Nein | Nein |
| A [1,:] A | Nein |
A [1,:] ' A | Nein | Ja | Nein |

Ich schwöre, ich habe gerade eine Antwort darauf gepostet, aber irgendwie ist sie im Äther verschwunden. Das ist alles richtig. In der aktuellen Anordnung müssen Sie berücksichtigen, ob Sie einen Zeilen- oder einen Spaltenabschnitt verwenden und ob Sie links oder rechts multiplizieren, wenn Sie entscheiden, ob Sie transponieren möchten oder nicht (Spalten werden links transponiert, Zeilen sind rechts transponiert). In dem Vorschlag berücksichtigen Sie nur, auf welcher Seite Sie multiplizieren - Sie transponieren immer links, niemals rechts.

Wäre es okay wenn

dot(x,y) und dot(x.',y) und dot(x,y.') und dot(x.',y.') geben alle den gleichen Skalar an?

dh Σᵢ konj (xᵢ) * yᵢ

Auf diese Weise kann man Punkte (x, A * y) machen, ohne zu viel nachzudenken

Diese Beispiele von @alanedelman wirken aufgrund der APL-Indizierung an den Stellen, an denen Sie dies nicht A[i; :] ?)

Aber dann möchten Sie, dass das im obigen Fall einen Covector ergibt.

@alanedelman , ich sehe keinen Grund, warum diese Methoden von dot nicht existieren sollten und das gleiche Ergebnis liefern.

Ich war immer in der Lage, allgemeine Kontraktionen mit Umformen, Permutationen und vielleicht Komplexen durchzuführen
Konjugate bei Bedarf mehr oder weniger wie oben

Genau so wird es in der Tensorcontract-Funktion in TensorOperations.jl implementiert, wenn Sie die BLAS-Methode wählen, die für große Tensoren sicherlich die schnellste ist. Ich habe auch eine native Julia-Implementierung geschrieben, die die Cartesian.jl-Funktionalität verwendet (und hoffentlich eines Tages abgestufte Funktionen verwendet), die die Notwendigkeit von Permutationen (zusätzliche Speicherzuweisung) eliminiert und für kleinere Tensoren schneller ist.

Ich habe nur auf die falsche Behauptung reagiert, dass matlab integrierte Funktionen dafür bietet, was Julia nicht tut. Sowohl die Umformung als auch die Permutimierung sind in Julia verfügbar. Numpy hat zwar die Tensordot-Funktion, die genau dies tut, aber Sie können die Indexreihenfolge des Ausgabetensors nicht angeben, sodass Sie danach immer noch permutierte Werte benötigen, wenn Sie eine bestimmte Ausgabereihenfolge im Auge hatten.

Dies ist jedoch zu weit vom aktuellen Thema entfernt, nämlich ein konsistentes Verhalten für die Vektor- und Matrixalgebra.

+1 für Stefans Vorschlag. Es scheint eine äußerst klare, aber ausreichend flexible Semantik zu geben. Als Benutzer einer linearen Algebra, selbst wenn er an die Syntax im MATLAB-Stil gewöhnt ist, finde ich die Regeln einfach genug, um sie zu verwenden.

Ich bin ein wenig verwirrt darüber, was ' im Allgemeinen bedeuten soll. Wenn v ein Vektor ist, ist v' ein transpose . Wenn a ein 2D-Array ist, wäre a' jetzt auch ein transpose . Diese beiden scheinen im Interesse einer einfachen Definition von b' * a indem die erste Dimension von b mit der ersten Dimension von a kontrahiert wird.

Es scheint keinen Konsens über eine Definition von a' wenn die Dimension von a > 2 ist. Ich habe niemanden etwas anderes vorschlagen hören, als die Dimensionen umzukehren, und dies fällt damit zusammen, dass b' * a die erste Dimension von b mit der ersten Dimension von a kontrahiert.

Ich denke, es wäre schön, wenn wir kurz und bündig angeben könnten, was ' macht, ohne auf die Größe des Objekts Bezug zu nehmen, mit dem es arbeitet.

Es scheint vernünftig , eine weitere Kontraktion Funktion , die bei Base für weitere allgemeine Situationen zu haben, zB contract(a, b, 2, 3) die zweite Dimension schrumpfen a mit dem 3. b .

Übrigens, dot(a,b) == a'*b wenn a und b Vektoren sind, aber dot(a,b) ist derzeit für Matrizen nicht definiert. Könnten wir dot(a,b) = trace(a'*b) ?

@ Madeleineudell : Ich bin ein wenig verwirrt darüber, was 'im Allgemeinen bedeuten soll.

Ich teile diese Sorge. Grundsätzlich können Sie in meinem Vorschlag die Eigenschaften 2-5, 7, 8 und 10 als definierende Merkmale verwenden. Dh du willst das halten:

  • v' ist eindimensional, aber kein Vektor
  • v'' === v
  • v' == v
  • v'w ist ein Skalar
  • v*w' ist eine Matrix
  • v'*M ist das Gleiche wie v'
  • M' ist zweidimensional, aber keine Matrix, vielleicht eine Matrixansicht

Insbesondere gibt es keine Einschränkungen hinsichtlich der Bedeutung oder Funktionsweise für höherdimensionale Arrays. Eine allgemeine Theorie darüber, was Covektoren sind, die höhere Dimensionalitäten umfasst, wäre schön, aber ich bin nicht davon überzeugt, dass dies wirklich möglich ist, ohne die Dinge zu komplizieren, z. B. indem Auf- / Ab-Dimensionen oder jede Dimension durch einen Index gekennzeichnet wird.

Zumindest ist klar, dass die allgemeine Regel für ' nicht lautet, dass die Dimensionen umgekehrt werden, da dies für Vektoren und Covektoren nicht der Fall ist.

Die einzige einfache Regel, die ich mir vorstellen kann, die das obige Verhalten sowohl für Vektoren als auch für Covektoren und Matrizen erfasst, besteht darin, die ersten beiden Dimensionen zu permutieren (ein Vektor hat eine fehlende zweite Dimension und ein Covektor hat eine fehlende erste und eine vorhandene zweite Dimension).

Am 20. Oktober 2014, um 09:05 Uhr, schrieb toivoh [email protected] :

Zumindest ist klar, dass die allgemeine Regel für 'nicht ist, dass es die Dimensionen umkehren würde, da dies für Vektoren und Covektoren nicht der Fall ist.

Die einzige einfache Regel, die ich mir vorstellen kann, die das obige Verhalten sowohl für Vektoren als auch für Covektoren und Matrizen erfasst, besteht darin, die ersten beiden Dimensionen zu permutieren (ein Vektor hat eine fehlende zweite Dimension und ein Covektor hat eine fehlende erste und eine vorhandene zweite Dimension).

Ich würde argumentieren, dass dies nicht wahr ist, wenn Sie über allgemeine Tensoren nachdenken möchten. Es könnte wahr sein, wenn Sie Matrizen und Vektoren nur als Blöcke mit Zahlen betrachten und v als Spalte und v 'als Zeile.

Wenn Sie sich jedoch v als ein Element eines Vektorraums und w = v 'als eine Möglichkeit vorstellen, v auf ein Element w des dualen Raums V_ abzubilden, dann ist w immer noch ein Vektor, dh ein eindimensionales Objekt. Im Allgemeinen benötigt man eine Metrik, um diese Zuordnung von V zu V_ zu definieren, dh w_i = g_ {i, j} v ^ j. Wenn nun V ein kartesischer Raum ist, dh R ^ n mit der euklidischen Standardmetrik, dann ist V * natürlich isomorph zu V. Dies bedeutet, dass es keine Vorstellung von oberen oder unteren Indizes (Kovariante oder Kontravariante) gibt und daher w_i = v_i = v ^ i = w ^ i. Ich würde argumentieren, dass dies der Fall ist, der in den meisten Programmen verwendet wird, dh der Fall, der von Julia Base unterstützt werden muss. (Für einen komplexen Vektorraum ist V * natürlich isomorph zu Vbar, dem konjugierten Vektorraum, und daher ist die natürliche Abbildung w_i = konj (v ^ i).)

Die Konvention, Vektoren als Spalten mit Zahlen, Doppelvektoren als Zeilen mit Zahlen und damit Matrizen (die Elemente von V \ otimes V_ sind) als Blöcke mit Zahlen zu bezeichnen, ist äußerst praktisch, hört jedoch auf, sobald Sie auch höhere Dimensionen berücksichtigen möchten Arrays, dh Elemente von Tensorprodukträumen höherer Ordnung. In diesem Fall ist ein "Zeilenvektor", dh ein zweidimensionales Objekt, bei dem die erste Dimension die Größe 1 hat, um es in der Matlab / Julia-Terminologie zu sagen, ein Element eines Tensorproduktraums V1 \ otimes V2, in dem V1 gerade passiert sei R (oder ein anderer eindimensionaler Raum). Ich bin damit einverstanden, dass dies möglicherweise nicht das ist, was Sie als Standardverhalten wünschen, aber dann würde ich es vorziehen, wenn man nicht versucht, die Transponierung für ein allgemeines Array zu definieren und auf permutierte Elemente zu verweisen, wie dies bei matlab der Fall ist. Das Umkehren der ersten beiden Dimensionen für einen allgemeinen Tensor als Standardkonvention ist nicht sinnvoll. Transponieren eines Elements aus einem Tensorproduktraum höherer Ordnung V1 \ otimes V2 \ otimes… \ otimes Vn hat keine eindeutige Definition. Die Konvention, alle Dimensionen umzukehren, ergibt sich nur aus einer praktischen grafischen Darstellung von Penrose, wie oben erwähnt. Außerdem ist dies derjenige, der den Matrixraum (V \ otimes V_) auf sich selbst abbildet (V * \ otimes V * = V \ otimes V ).

Ich kann zwei Wege vorwärts sehen:
1) Lassen Sie den Convenience-Operator (und vielleicht sogar *) mit beliebigen Arrays höherer Ordnung unter Verwendung einer ausgewählten Konvention innerhalb der Einstellung kartesischer Tensoren arbeiten (dh keine Unterscheidung zwischen oberen oder unteren Indizes). Dies könnte einige überraschende Ergebnisse für typische Anwendungsfälle bringen.

2) Beschränken Sie 'und * auf Vektoren und Matrizen. Fehler bei Arrays höherer Ordnung. Ich denke, dies ist der beliebteste Ansatz (z. B. Matlab usw.).

Dieser Beitrag ist ein bisschen so, als würde man die andere Seite von der Position nehmen, die ich letztes Jahr eingenommen habe, um

Entdecken Sie die Ambivalenz von beiden Seiten.

Hoffe das ist okay.
Endlich wurde mir klar, dass der aktuelle Ansatz eine Logik hat, die aber nie artikuliert wurde.
Es sah so sehr nach einem Hack aus, dass es mich nur ärgerte. Irgendwie jetzt, wo ich verstehe
es gefällt mir besser.

Beim aktuellen Ansatz sind alle Arrays unendlich dimensional mit implizierten fallenden Einsen.
Der Apostrophoperator hätte bedeuten können, die ersten beiden Dimensionen auszutauschen.
aber in der Tat, wie es heute existiert, bedeutet es

ndim (A) <= 2? interchange_first_two_dims: no_op

Entschuldigung, wenn alle anderen das gesehen haben und ich es einfach verpasst habe. Mein Verstand war festgefahren
mit der Idee, dass Vektoren eindimensional, nicht unendlich dimensional waren, und
Daher sollte eine Transponierte die Dimensionen umkehren und daher ein no_op sein.

Ich bin damit einverstanden, dass der Apostroph dies tut oder dass der Apostroph immer wechselt
die ersten beiden Dimensionen - ich glaube nicht, dass es mich interessiert. Ich denke, der Apostroph existiert
für lineare Algebra und nicht multilineare Algebra.

Ich spielte damit, ob Apostroph-Stern ("'*") (wenn möglich! Oder etwas anderes, wenn unmöglich)
sollte bedeuten, die letzte Dimension zur ersten Dimension zusammenzuziehen (wie der Punkt von Mathematica)
und haben apl Indexierung und keine Covektoren. Ein Teil meines Gehirns denkt immer noch, dass es sich lohnt, ihn zu erkunden.
aber als ich aufwache dies ist der gegenwärtige Ansatz besser ist scheinbar und besser.
(Mal sehen, wie ich mich heute später fühle)

Ich bereue nicht, dieses Thema im letzten Jahr starten, aber ich frage mich wieder welche Fälle
Leute heute wirklich nerven ... können wir eine Liste von Beispielen bekommen? ... und ob
Licht in diese Fälle zu bringen ist besser als zu ändern, was wir tun.

Ich habe diesen ganzen Thread durchgelesen und viele, viele Prinzipien gesehen - was gut ist,
aber nicht genug Anwendungsfälle, um mich mit Dingen zu beschäftigen.

Ich kann sagen, dass mich das oft geärgert hat

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

hauptsächlich, weil ich mich anscheinend nicht daran erinnern kann.

Nur ein Gedankenexperiment. Welche Funktionalität würde verloren gehen, wenn wir * neu definieren würden, um als Kontraktionsoperator zu dienen, ähnlich wie es @alanedelman für '* vorhat ? Würde es nicht die Notwendigkeit von ' in linearen algebraischen Operationen beseitigen (abgesehen davon, dass es als Transponierte für Matrizen fungiert)? Ich kann nur sehen, dass es uns das äußere Produkt w*v' entziehen würde, das leicht durch outer(w,v) .

EDIT: Angenommen, APL Slicing ist das. Zum Beispiel hätte A[1,:]*A*A[:,1] das erwartete Ergebnis, ohne dass der erste Operand mit ' transponiert werden müsste.

Daran habe ich auch gedacht. Ich denke, dass v * w ein Punktprodukt ist, das nur wie eine Überladung von Steroiden erscheint
das kann fehleranfällig sein.
Dies ist ein Ort, an dem der Punkt von Mathematica nicht so schlecht zu sein scheint.

Zusammenfassend scheint ein Vertrag von vornherein vernünftig, aber
ob es ein Apostrophstern sein könnte oder * sein könnte oder Punkt sein sollte
hat Probleme.

Etwas unabhängig, aber nicht völlig unabhängig ....
Ich möchte darauf hinweisen, dass der Punktstern, den jeder ursprünglich als Punktstern liest (wie mir gesagt wurde), war
sollte Point-Star sein, weil der POINTWISE-Operator was war
war gemeint

Ich kann sagen, dass mich das oft geärgert hat

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

hauptsächlich, weil ich mich anscheinend nicht daran erinnern kann.

Ich habe immer gelernt, "warum verwenden wir nicht nur , ?"

Welche Funktionalität würde verloren gehen, wenn wir * neu definieren würden, um als Kontraktionsoperator zu dienen, ähnlich dem, was @alanedelman für '* im Sinn hat?

Wir würden die Assoziativität verlieren: zB würde (M*v)*v das aktuelle dot(v,M*v) (einen Skalar) geben, während M*(v*v) M.*dot(v,v) (eine Matrix) geben würde.

Warum machen wir nicht einfach dot zum Kontraktionsoperator (was sowieso nicht assoziativ ist)? Wir könnten auch Kontraktionen höherer Ordnung definieren, z. B. ddot(A::Matrix,B::Matrix) == A⋅⋅B == trace(A'*B) .

Verstehe ich also richtig, dass der einzige Zweck der Einführung von Vektortransponierungen darin besteht, uns vor der Nichtassoziativität von * zu bewahren? Die Mehrdeutigkeit des Beispiels von @alanedelman könnte durch Transponieren eines der Vektoren ( M*v*v' vs M*v'*v ) behoben werden, aber das gleiche kann mit Klammern ( M*(v*v) vs (M*v)*v ) ohne den Aufwand, die Transponierung für Vektoren zu implementieren (wie @Jutho bereits darauf hingewiesen hat, ist die Implementierung der Transponierung für Tensoren höherer Ordnung mathematisch ohnehin nicht sinnvoll). Für mich ist die Frage, welche Notation lesbarer / prägnanter / eleganter / reiner ist.

Tatsächlich werden M*(v*v) und (M*v)*v derzeit beide als analysiert

*(M, v, v)

Um ein Matrixprodukt zu ermöglichen, das die Reihenfolge der Multiplikation basierend auf der Größe der Argumente optimiert, müsste auch der Parser geändert werden.

Aber zumindest persönlich denke ich, dass Assoziativität eine ziemlich große Sache ist. Sie müssen zwischen Mathematik und den meisten Programmiersprachen übersetzen. Für Julia hoffe ich immer noch, dass Sie nicht viel übersetzen müssen.

(Wie @Jutho bereits

Ich glaube nicht, dass ich genau das gesagt habe.

Welche Funktionalität würde verloren gehen, wenn wir * neu definieren würden, um als Kontraktionsoperator zu dienen, ähnlich dem, was @alanedelman für '* im Sinn hat?

Wir würden die Assoziativität verlieren: zB würde (M_v) _v den aktuellen Punkt (v, M_v) (einen Skalar) ergeben, während M_ (v_v) M._dot (v, v) (eine Matrix) ergeben würde.

Warum machen wir dot nicht einfach zum Kontraktionsoperator (was sowieso nicht assoziativ ist)? Wir könnten auch Kontraktionen höherer Ordnung definieren, z. B. ddot (A :: Matrix, B :: Matrix) == A⋅⋅B == trace (A '* B).

Mit dieser Argumentation brauchen wir den Multiplikationsoperator für Vektoren und Matrizen nicht mehr, wir können einfach alles in Punkt schreiben:
A ∙ B.
A ∙ v
v ∙ A ∙ w
v ∙ w

Das ist also nur der @ pwl- Vorschlag, aber mit * durch Punkt ersetzt.

Aber zumindest persönlich denke ich, dass Assoziativität eine ziemlich große Sache ist. Sie müssen zwischen Mathematik und den meisten Programmiersprachen übersetzen. Für Julia hoffe ich immer noch, dass Sie nicht viel übersetzen müssen.

Ich denke, ein Teil des Problems ist, dass es selbst in der Mathematik unterschiedliche Konventionen gibt. Denkt in Zeilen- und Spaltenvektoren Mathematik oder wollen wir das abstraktere Verhalten in Bezug auf lineare Operatoren und duale Räume (wo ich denke, ein Operator wird nicht mit einem Vektor multipliziert, sondern auf einen Vektor angewendet, warum also nicht schreiben? A (v) anstelle von A * v oder A ∙ v)?

Ich brauche keine Convenience-Syntax, ich persönlich bevorzuge es, jedes Mal einen Punkt (v, w) über v '* w zu schreiben, da erstere die basenunabhängige mathematische Operation klarer ausdrückt (tatsächlich muss man zuerst a definieren Skalarprodukt vor einer natürlichen Abbildung von Vektoren auf Doppelvektoren kann sogar definiert werden). =

Tut mir leid, dass ich so unwissend bin, aber warum kann M[1, :] genau eine Transponierte sein, die sich wie v' verhält?

Ich werde meiner Liste der Dinge hinzufügen, die mich nerven

1.

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

2.
v=rand(3)
v' * v derzeit ndims == 1 ( @StefanKarpinskis Vorschlag behebt dies)
(v' * v)/( v' * v ) hat ndims ==2 (das nervt mich wirklich sehr und würde auch behoben werden)

Trotzdem will ich keine Covektoren
und apl indizierung ist etwas, worauf ich immer wieder hin und her gehe
--- denke immer noch nach, aber ich würde gerne die Liste der Dinge sehen, die
Bug andere Leute in der aktuellen Julia

Ich mag definitiv die Idee von cdotKontrahieren des letzten Index zum ersten Index zusätzlich zum Operator *
und das Zulassen von Punkten (a, b, ..) ermöglicht sehr allgemeine Kontrationen.

Trotz Numpys Namenskonvention (und vielleicht des Aussehens meines vorherigen Beitrags) habe ich etwas gemischte Gefühle in Bezug auf die Verwendung von Punkten für allgemeine Tensorkontraktionen. Dies ist der erste Satz von Wikipedia:

In der Mathematik ist das Punktprodukt oder Skalarprodukt (oder manchmal das innere Produkt im Kontext des euklidischen Raums) eine algebraische Operation, die zwei gleichlange Folgen von Zahlen (normalerweise Koordinatenvektoren) verwendet und eine einzelne Zahl zurückgibt.

Auch in meinen Augen sollte dot einen Skalar zurückgeben.

@ brk00 , das Problem mit Ihrer Frage ist, dass nicht klar ist, wie dies auf Slices von höherdimensionalen / höherrangigen Arrays (ich mag die

Tut mir leid, dass ich so unwissend bin, aber warum genau kann M [1,:] keine Transponierte sein und sich wie v 'verhalten?

Es kann, aber wie verallgemeinern Sie das?

@ Jutho , es tut mir leid, ich hätte es anders formulieren sollen. Ich meinte Ihre Zeile "Das Transponieren eines Elements aus einem Tensorproduktraum höherer Ordnung [...] hat keine eindeutige Definition", daher gibt es keine mathematische Motivation, eine bestimmte Definition einzuführen oder überhaupt zu definieren.

@ Alanedelman :

Ich werde meiner Liste der Dinge hinzufügen, die mich nerven

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

Das Verkettungsverhalten hat nichts mit diesem Problem zu tun. Lassen Sie uns das Wasser nicht weiter trüben.

Ich mag definitiv die Idee, dass cdot zusätzlich zum Operator * den letzten Index zum ersten Index zusammenzieht
und das Zulassen von Punkten (a, b, ..) ermöglicht sehr allgemeine Kontrationen.

Dies ist auch tangential. Konzentrieren wir uns weiterhin auf Arrays und Slicing.


Beim aktuellen Ansatz sind alle Arrays unendlich dimensional mit implizierten fallenden Einsen.

Das ist nicht wirklich wahr. Wenn dies wahr wäre, gäbe es keinen Unterschied zwischen ones(n) , ones(n,1) , ones(n,1,1) usw. Aber das sind alles verschiedene Objekte mit unterschiedlichen Typen, die nicht einmal gleich sind andere. Wir bemühen uns, die Dinge so zu implementieren, dass sie sich ähnlich verhalten, aber das ist weit davon entfernt, dass Arrays tatsächlich unendlich dimensional sind.


Die Liste der Dinge, die derzeit störend sind, spiegelt weitgehend (eine Teilmenge von) die schönen Eigenschaften meines obigen Vorschlags wider. Nämlich:

  1. asymmetrisches Schneidverhalten - Nachlaufabmessungen sind besonders.
  2. v'' !== v - tatsächlich v'' != v ; Das liegt daran, dass sie nicht den gleichen Rang haben.
  3. v' != v - gleiches Angebot.
  4. v'w ist ein Ein-Element-Vektor, kein Skalar.
  5. Wir brauchen eine spezielle Analyse für A*_mul_B* , was die schrecklichste Sache überhaupt ist.

Ich stimme den meisten Punkten von @StefanKarpinski zu , mit Ausnahme des v' == v -Bits : Ich denke nicht, dass diese gleich sein sollten. Ja, sie mögen isomorph sein, aber sie sind immer noch unterschiedliche Objekte, da sie sich sehr unterschiedlich verhalten: Die Matrizen M und M' sind ebenfalls isomorph, aber ich glaube nicht, dass wir jemals wollen würden, dass sie gleich sind (es sei denn, sie sind natürlich hermitisch).

Meine allgemeine Ansicht mit den Covector -Typen war, dass sie relativ leicht sein sollten: Abgesehen von grundlegenden Operationen (Multiplikation, Addition usw.) würden wir vermeiden, zu viele Operationen zu definieren (ich würde sogar zögern, die Indizierung zu definieren ). Wenn Sie etwas damit machen wollen, sollten Sie es wieder in einen Vektor konvertieren und dort Ihre Sachen machen.

+1 auf @simonbyrnes Ansicht, einschließlich seiner allgemeinen Zustimmung zu @StefanKarpinskis Ansicht.

Stimmen Sie auch @simonbyrne zu.

Ja, v und v' haben unterschiedliche Typen, aber wir betrachten bereits unterschiedliche Array-Typen mit derselben Form und denselben Daten als gleich - zum Beispiel speye(5) == eye(5) . Warum unterscheidet sich Covector von Sparse?

Ich hoffe, dass Covectors wie Zeilenmatrizen senden würden. Für Arrays A und B , die als gleich angesehen werden, gilt dies bisher

all(A .== B)

was nicht der Fall wäre, wenn man ein Vektor und einer ein Covektor ist.

Für mich ist ein Covector eher eine Zeilenmatrix als eine Vektor- oder Spaltenmatrix.

Ich denke, eine Schlüsselfrage ist, was ist size(v' )? Wenn die Antwort (length(v),) dann denke ich, dass v == v' wahr sein sollte. Wenn size(v') == (1,length(v)) dann sollte es wahrscheinlich falsch sein, aber wohl dann sollte v' == reshape(v,1,length(v)) wahr sein. Die Frage ist also, ob v' eine spezielle Art von Vektor oder eine spezielle Art von Matrix sein soll.

Zunehmend bin ich davon überzeugt, dass es in dieser Ausgabe wirklich darum geht, was Transponieren wirklich bedeutet.

Covectors sind keine wirklichen Arrays, und ich glaube nicht, dass wir wirklich sagen können, welche "Form" sie haben. Ein Covector wird wirklich durch das definiert, was er tut, das heißt, *(::Covector, ::Vector) gibt einen Skalar: Da AbstractVector das nicht tut, ist es nicht wirklich dasselbe.

Ein weiteres Problem wäre im komplexen Bereich: Hätten wir v' == v oder v.' == v ?

@ Simonbyrne :

Ein Covector wird wirklich durch das definiert, was er tut, das heißt, *(::Covector, ::Vector) gibt einen Skalar: Da AbstractVector das nicht tut, ist es nicht wirklich dasselbe.

Das ist ein wirklich guter Punkt. Es kann jedoch ziemlich frustrierend sein, wenn v' nicht als Objekt verwendet werden kann. Vielleicht ist der richtige Ansatz, v' als lustige Zeilenmatrix anstelle eines lustigen Vektors zu behandeln.

Vielleicht besteht der richtige Ansatz darin, v 'als lustige Zeilenmatrix anstelle eines lustigen Vektors zu behandeln.

Irgendwie, aber ich denke auch nicht, dass es ein Subtyp von AbstractMatrix sein sollte: Ich denke, AbstractCovector sollte ein Typ der obersten Ebene sein. Ich würde gerne length(::Covector) , aber ich denke nicht, dass wir eine size -Methode definieren sollten.

Ich bin mir nicht sicher, wie ich mit Rundfunk umgehen soll: Wenn wir keine vernünftigen Kriterien festlegen können, würde ich mich irren, wenn ich keine Rundfunkmethoden definiere.

Diese Diskussion scheint auf die Verwendung von Transponierten und Vektoren zu konvergieren, wie sie in der Technik verwendet werden, dh alles als Matrizen zu betrachten. Stellen Sie sich Vektoren als Spalte und Konvektoren als Zeile vor. Dies ist gut für Vektoren und lineare Karten im kartesischen Raum (der typische Anwendungsfall), beginnt jedoch zu scheitern, wenn man versucht, auf multilineare Algebra oder allgemeinere Vektorräume usw. zu verallgemeinern. Es scheint viele Verwirrungen zu geben, die sich aus der Tatsache ergeben, dass z kartesische Räume Viele Dinge sind gleichwertig, die für allgemeine Räume nicht gleichwertig sind. Ich bin nicht unbedingt dagegen als Julias Standardverhalten, aber ich bin mit einigen der obigen Aussagen wirklich nicht einverstanden, als ob sie „mathematisch korrekt“ wären. Lassen Sie mich den folgenden widersprechen.

Am 20. Oktober 2014, um 17:39 Uhr, schrieb Simon Byrne [email protected] :

Ich stimme den meisten Punkten von @StefanKarpinski zu , mit Ausnahme des v '== v-Bits: Ich denke nicht, dass diese gleich sein sollten. Ja, sie mögen isomorph sein, aber sie sind immer noch unterschiedliche Objekte, da sie sich sehr unterschiedlich verhalten: Die Matrizen M und M 'sind ebenfalls isomorph, aber ich glaube nicht, dass wir jemals wollen würden, dass sie gleich sind (es sei denn, sie sind natürlich hermitisch).

Diese Aussage macht auf keiner Ebene Sinn.

1) Ich denke, Sie missbrauchen hier die Bedeutung von isomorph. Isomorph ist eine Beziehung zwischen zwei Räumen (in dieser Einstellung). Im Allgemeinen gibt es für jeden (realen) Vektorraum V einen dualen Raum V * linearer Funktionen (für komplexe Räume gibt es auch den konjugierten Raum und das duale des konjugierten Raums). Dies sind typischerweise unterschiedliche Räume, und es gibt nicht einmal eine eindeutige oder natürliche Zuordnung von einem zum anderen, dh es gibt im Allgemeinen keine Bedeutung, Elemente v von V Elementen phi von V * zuzuordnen. Die einzige allgemeine Operation ist das Anwenden der linearen Funktion, dh phi (v) ist ein Skalar.

Eine natürliche Abbildung von V auf V * kann definiert werden, sobald Sie eine bilineare Form V x V -> Skalare haben, typischerweise ein inneres Produkt / eine innere Metrik. Man kann dann phi_i = g_ {i, j} v ^ j definieren.

2) gibt es tatsächlich keine natürliche Operation, die mit dem "Transponieren eines Vektors" verbunden ist (siehe Punkt 3). Diese Verwirrung ergibt sich aus der Identifizierung von Vektoren mit Spaltenmatrizen.
Wenn ich es wahrscheinlich zu stark formuliere, würde ich gerne einen Anwendungsfall für die Transponierung eines Vektors sehen, den Sie mit Punkt und möglicherweise einer Operation des äußeren Produkts / Tensorprodukts nicht erhalten können.

Wenn Sie jedoch die Transponierung verwenden möchten, um Vektoren auf Doppelvektoren abzubilden, dh um v in V auf ein phi = v 'in V_ abzubilden, gehen Sie davon aus, dass Sie mit dem euklidischen Standardinnenprodukt (g_ {) arbeiten i, j} = delta_ {i, j}). An diesem Punkt heben Sie die Unterscheidung zwischen kovarianten und kontravarianten Indizes auf, arbeiten im Wesentlichen mit kartesischen Tensoren und V und V_ werden von Natur aus isomorph. Wie oben erwähnt, ist w_i = v ^ i = v_i = w ^ i, also ja, v == w und ich würde sogar sagen, dass es nichts gibt, das diese beiden unterscheidet.

3) Die Operation "transponieren" ist ursprünglich nur für lineare Karten von V -> W (http://en.wikipedia.org/wiki/Dual_space#Transpose_of_a_linear_map) definiert, und selbst dort bedeutet sie möglicherweise nicht, was Sie denken. Die Transponierung einer linearen Karte A: V-> W ist eine Karte A ^ T von W_-> V_, dh sie wirkt auf Vektoren in W_, auf Doppelvektoren und erzeugt Elemente von V_, dh Covektoren von V. Dies bedeutet, Wenn Sie es als die übliche Transponierung A ^ T einer Matrix A betrachten, ist diese Matrix A ^ T mit Spalten zu multiplizieren, die Vektoren in W * darstellen, und dass die Spalte, die daraus hervorgeht, einen Covektor von V darstellt Zu diesem Zeitpunkt schlägt die Identifizierung von Doppelvektoren mit Zeilenvektoren bereits fehl.

Im typischen Anwendungsfall realer Vektorräume, in denen V * und W * über das euklidische Standardinnenprodukt mit V und W identifiziert werden, kann die Transponierte einer linearen Karte jedoch mit dem Zusatz dieser linearen Karte identifiziert werden, was tatsächlich der Fall ist Eine Karte von V-> W, wie die meisten Leute denken, ist auch für die Transponierung der Karte der Fall. Im komplexen Fall schlägt dies fehl, da eine gewöhnliche Matrixtransposition (ohne komplexe Konjugation) nur als Karte W_-> V_ sinnvoll ist, als Karte W-> V keine basenunabhängige Definition und daher operativ nicht sinnvoll ist.

Was Transponieren für höherdimensionale Arrays bedeuten sollte, sollte innerhalb des Matlab / Engineering-Ansatzes, zu dem wir konvergieren, ein Fehler sein, der nicht verallgemeinert wird. Dies bedeutet nicht, dass es keine Möglichkeit gibt, es zu definieren. Das Problem ist, was ein Array höherer Ordnung darstellt. Ist es ein Element eines Tensorproduktraums V1 \ otimes V2 \ otimes… \ otimes VN, ist es eine mehrlineare Abbildung, die auf V1 x V2 x… x VN wirkt, ist es eine lineare Abbildung aus einem Tensorproduktraum V1 \ otimes V2 \ otimes … \ Otimes VN zu einem anderen Tensorproduktraum W1 \ otimes W2 \ otimes… \ otimes WM? Für den zweidimensionalen Fall gibt es eine Auflösung. Identifizieren linearer Karten A: V-> W mit Vektoren in W \ otimes V_, Transposition im linearen Kartensinn entspricht der Umkehrung der Räume in diesem Tensorproduktraum, A ^ i_j -> A_j ^ i mit A ^ T in V_ \ otimes W = V * \ otimes W _, was in der Tat eine Karte von W_ -> V ist . Das Schöne am Umkehren von Dimensionen für Objekte höherer Ordnung ist, dass es eine bequeme grafische Darstellung hat.

Zusammenfassend denke ich, dass das Problem darin besteht, ob Julias Vektor die Eigenschaften eines beliebigen allgemeinen Vektors im mathematischen Sinne erfassen muss oder ob er nur eine Zahlenspalte, eine Liste, ... darstellt. Die Wörter Vektor, Tensor, ... haben gut -definierte operative und basenunabhängige Bedeutungen in der Mathematik, die möglicherweise mit der Art von Operationen in Konflikt stehen, die Sie auf einem Julia-Vektor definieren möchten. Umgekehrt sind einige Objekte aus mathematischer Sicht tatsächlich Vektoren (Doppelvektoren usw.), die wahrscheinlich nicht mit Julia-Vektoren identifiziert werden sollten, da der Standard-Vektortyp Julia (Zusammenfassung) keine Möglichkeit bietet, zwischen verschiedenen Vektorräumen zu unterscheiden.

In dieser Hinsicht gibt es eine gewisse Asymmetrie, da der Begriff Matrix viel weniger überlastet ist, selbst aus mathematischer Sicht ist eine Matrix nur eine bequeme Darstellung einer linearen Karte auf einer bestimmten Basis. Beachten Sie, dass es auch viele Fälle gibt, in denen Sie eine lineare Karte nicht als Matrix, sondern als Funktion darstellen möchten (dieses Problem ist bereits in den Argumenten von Eigs usw. aufgetreten). In dieser Hinsicht kann ich sehen, warum sich matlab nicht einmal die Mühe gemacht hat, einen Vektor zu haben, eine wirklich eindimensionale Struktur. Bei diesem Ansatz wird alles nur als 'Matrix' interpretiert, dh als Block mit Zahlen.

Frage? Sei c ein Covector. Was ist fft (c)?

Antwort: fft (c ')', das komplexe Konjugate auf möglicherweise unerwartete Weise aufnimmt
im Vergleich zu fft (c)

Benutzer können nur davon profitieren, dass es undefiniert ist
(oder vielleicht wäre dies gut für Benutzer, wenn gut dokumentiert?)

Ich wette, es gibt noch viel mehr solcher Dinge

Ich vermute, dass das Richtige für Base Moment darin besteht, nur Folgendes zu definieren:

  • Covector mal einen anderen Vektor
  • Covector mal eine Matrix
  • covector ', um einen Vektor zu erhalten

+1 bis minimale Unterstützung für Covectors vorerst.

Ja, +1.

Wenn ich das erwähne, bin ich mir ziemlich sicher
Norm (Covector, q) sollte Norm (Vektor, p) sein, wobei 1 / p + 1 / q = 1 ist
Aufgrund der Ungleichheit der Inhaber würde dies für eine lange Zeit nicht umgesetzt werden. :-)

Dem Himmel sei Dank für p = q = 2

Das wäre gar nicht so schwer umzusetzen:

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

Sie möchten wahrscheinlich zusätzliche Überprüfungen, um q == 0 und q == 1 zu vermeiden.

Ich denke q == 1 wäre okay

Med venlig hilsen

Andreas Noack

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

Das wäre gar nicht so schwer umzusetzen:

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

Sie möchten wahrscheinlich eine zusätzliche Überprüfung, um q == 0 und q == 1 zu vermeiden.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/JuliaLang/julia/issues/4774#issuecomment -60139762.

Ich arbeite an einer Beschreibung des Covector-Vorschlags (den ich größtenteils unterstütze), aber es kann nützlich sein, jetzt einen Punkt zu veröffentlichen, der @StefanKarpinskis Vorstellung von "lustiger Zeilenmatrix" präzisiert.

Covektoren sind keine Vektoren, aber die Beschreibung des Grundes ist nicht immer klar. Ein Covektor (oder BH-Vektor, wie Physiker ihn nennen würden) ist eine lineare Funktion, die einen Vektor frisst und eine Zahl ausspuckt, die ein Punktprodukt ist.

Etwas präziser:

  • Sei V = V(F) und W = W(F) ein Vektor über einem Feld von Elementen F ,
  • v und w sind Vektoren, die Elemente von V bzw. W sind.
  • <.,.> ist ein inneres Produkt, so dass <.,.> : V × W → F und v, w ↦ <v, w>

Der Covektor, der v ist die lineare Funktion v' : W → F , die das Mapping w ↦ <v, w> ausführt.

Die übliche Beschreibung endet mit der Aussage, dass v' ein Element eines dualen Raums V* wobei nicht viel anderes darüber gesagt wird, was der duale Raum _ ist_.

Für Informatiker gibt es eine einfache Beschreibung von v' als Curry des inneren Produkts in Bezug auf seinen ersten Parameter und V* als Sammlung von Funktionen, die ein Parameter sind Currys mit verschiedenen ersten Vektoren.

Dieser Wikipedia-Artikel kann hilfreich sein, um die verschiedenen Notationen, die mit den beiden Beschreibungen verbunden sind, in Einklang zu bringen, aber sie drücken genau das gleiche Konzept aus.

Außerdem dachte ich anfangs, dass das Indizieren in einen Covector nicht erlaubt sein sollte. Das Indizieren von v'[1] entspricht jedoch dem Anwenden von v' auf den kanonischen Basisvektor e₁ = (1, 0, ...) , sodass v'[1] als <v, e₁> . Allgemeinere Indizierungssemantiken wie 1:n natürlich aus der Anwendung von v' auf eine geeignete Projektionsmatrix, wobei jede Spalte ein kanonischer Basisvektor ist.

Die Betrachtung der Indizierungssemantik von Covektoren im Kontext von # 987 gibt einen weiteren Grund, warum Covektoren nicht AbstractVector : Es ist im Allgemeinen nicht möglich, v' billig zu indizieren, da alles davon abhängt, wie teuer die Mengen sind wie <v, e₁> sind zu berechnen. Im Allgemeinen könnte ein inneres Produkt in Bezug auf eine Matrix <v, w> = v'*A*w und die Kosten für die Indizierung werden von dem matvec-Produkt A*w (oder A'*v ) dominiert, was sicherlich der Fall ist zu teuer, um sich als AbstractVector .

Da wir über DFTs und Normen sprechen ... kam mir das heute in den Sinn

Wenn f eine Funktion eines Vektors ist und einen Skalar erzeugt, möchte ich, dass diff(f) eine Funktion ist, die ein Vector akzeptiert und ein Covector so dass f(x) ~= f(x0) + diff(f)(x0) * (x-x0) . Eine sehr häufige Verwendung des Gradienten besteht darin, eine lineare Annäherung der inkrementellen Änderung in der Ausgabe der Funktion bei einer inkrementellen Änderung in der Eingabe zu erhalten. Wenn die Eingabe ein Vektor ist, fühlt es sich natürlich an, dass sich der Gradient entlang aller Dimensionen der Eingabe zusammenzieht.

Aber wenn ich einen Gradientenabstieg implementieren würde, müsste ich den Gradienten bei x zu sich selbst hinzufügen (eine skalierte Version davon). In diesem Sinne ist der Gradient ein Vektor, der in die steilste Richtung zeigt, dessen Norm proportional zur Änderungsrate entlang dieser steilsten Richtung ist.

Mein Bauch sagt, dass "der Gradient der Vektoreingabefunktion an einem Punkt ein Covektor ist" wichtiger ist.

Ich vermute, dass das Richtige in Base vorerst darin besteht, nur Folgendes zu definieren:

Covector mal einen anderen Vektor
Covector mal eine Matrix
covector ', um einen Vektor zu erhalten

Trotz des Eindrucks, den Sie von meinen vorherigen schrecklichen Beiträgen bekommen haben könnten, +1 dazu.

Dennoch einige Anmerkungen dazu:

Ich arbeite an einer Beschreibung des Covector-Vorschlags (den ich größtenteils unterstütze), aber es kann nützlich sein, jetzt einen Punkt zu veröffentlichen, der @StefanKarpinskis Vorstellung von "lustiger Zeilenmatrix" präzisiert.

Covektoren sind keine Vektoren, aber die Beschreibung des Grundes ist nicht immer klar. Ein Covektor (oder BH-Vektor, wie Physiker ihn nennen würden) ist eine lineare Funktion, die einen Vektor frisst und eine Zahl ausspuckt, die ein Punktprodukt ist.

Ich denke, dass duale Vektoren / Covektoren (ich bevorzuge auch den Begriff lineare Funktionale) definiert werden können, ohne ein inneres Produkt zu haben. Es ist nur so, dass Sie ein inneres Produkt benötigen, wenn Sie eine Zuordnung von V zu V * definieren möchten

Etwas präziser:

Sei V = V (F) und W = W (F) ein Vektor über ein Feld von Elementen F,
v und w sind Vektoren, die Elemente von V bzw. W sind,
<.,.> sei ein inneres Produkt, so dass <.,.>: V × W → F und v, w ↦
Es ist ziemlich seltsam und ich halte es für unmöglich, ein inneres Produkt zwischen zwei verschiedenen Vektorräumen V und W zu definieren. Wie definieren Sie die Eigenschaft der positiven Bestimmtheit?
Der Covektor, der v entspricht, ist die lineare Funktion v ': W → F, die die Abbildung w ↦ ausführt.

Die übliche Beschreibung endet mit der Aussage, dass v 'ein Element eines dualen Raums V * ist, wobei nicht viel anderes darüber gesagt wird, was der duale Raum ist.

Ich denke, es ist ziemlich klar, sicherlich für den endlichdimensionalen Fall, dass der duale Raum mit seinem Namen ein Vektorraum ist. Die Zusammenfassung meiner vorherigen Beschimpfungen lautet jedoch, dass Julias (abstrakter) Vektortyp eher einer Liste ähnelt. Es kann verwendet werden, um bestimmte Vektoren und andere eindimensionale Datenstrukturen darzustellen, die nicht die mathematische Struktur eines Vektors haben, aber es ist nicht allgemein genug, um alle Objekte zu erfassen, die mathematische Vektoren sind (da es nicht zwischen verschiedenen Vektoren unterscheiden kann Leerzeichen).

Die Betrachtung der Indizierungssemantik von Covektoren im Kontext von # 987 gibt einen weiteren Grund, warum Covektoren keine AbstractVectors sind: Es ist im Allgemeinen nicht möglich, v 'billig zu indizieren, da alles davon abhängt, wie teuer Mengen sind= v'_A_w und die Kosten für die Indizierung werden vom matvec-Produkt A_w (oder A'_v) dominiert, das sicherlich zu teuer ist, um als AbstractVector zu gelten.

Dies ist eine Art Schleifenargument. Wie oben erwähnt, ist eine lineare Funktion (Covector) allgemeiner und existiert sogar für Vektorräume ohne inneres Produkt. Zweitens, wenn die Metrik eine positive bestimmte Matrix A ist, ist die natürliche Abbildung von einem Vektor v auf einen Covektor tatsächlich v'_A. Was ist das hier? Ist es bereits eine Zuordnung von v zu einem anderen Covektor, der in Bezug auf die euklidische Standardnorm definiert wurde (mit einer Identität als Metrik)? Nein ist es nicht. Wenn Vektoren kontravariante (obere) Indizes haben und Covektoren kovariante (untere) Indizes haben, dann hat die Metrik A zwei untere Indizes und das innere Produkt ist= v ^ i A_ {i, j} v ^ j. Und so kann diese Abbildung als (mit phi der dem Vektor v zugeordnete Covektor) als phi_j = v ^ i A_ {i, j} geschrieben werden. (Beachten Sie, dass sich dies von einem typischen linearen Operator unterscheidet, der die Form w ^ i = O ^ i_j v ^ j hat.) Daher ist das v in diesem Ausdruck v'_A immer noch dasjenige mit einem oberen Index, es ist immer noch der gleiche Vektor.

Einer der Punkte, die ich oben ansprechen wollte, ist, dass Leute oft v 'schreiben, wenn sie nicht wirklich versuchen, mit einem Covector zu arbeiten. Sie versuchen nur, Ausdrücke zu schreiben, die auf Vektoren definiert sind, beispielsweise auf einem inneren Produktoder so etwas wie v ^ i A_ {i, j} w ^ j innerhalb der bekannten Matrixdarstellung als v'_A_w. Sogar das euklidische Standard-Innenprodukt v'w sollte als v ^ i delta_ {i, j} w ^ j gelesen werden und erfordert keine Einführung von Konvektoren. Wie oben erwähnt, denke ich, dass die Verwendung von tatsächlichen richtigen Covektoren, die keine versteckte Form von Skalarprodukten sind, in den meisten Anwendungen eher begrenzt ist. Die Leute schreiben einfach v ', um die bequeme Matrixsyntax verwenden zu können, aber was sie wirklich tun, ist die Berechnung innerer Produkte usw., die in Bezug auf Vektoren definiert sind, nicht in Bezug auf Covektoren.

Abgesehen davon gab es zuvor einige Bemerkungen zur Assoziativität der Multiplikation, wenn wir v '== v wie in Stefans ursprünglichem Vorschlag hätten. Selbst ohne dies würde ich jedoch nicht sagen, dass * assoziativ ist, selbst wenn v 'ein Covektor ist, der nicht mit gewöhnlichen Vektoren identifiziert wird:
A_ (v'_w) ist eine Matrix
(A_v ') _ w erzeugt einen Fehler

Der Gradient einer skalarwertigen Funktion ist in der Tat eine der richtigen Anwendungen von Covektoren, dh ohne Argument ist der Gradient ein Covektor. In Anwendungen wie dem konjugierten Gradienten wird implizit angenommen, dass es eine Metrik (und tatsächlich eine inverse Metrik) gibt, um diese Covektoren wieder in Vektoren abzubilden. Ansonsten macht es keinen Sinn, so etwas wie x ^ i (Positionsvektor) + alpha g_i (Gradient) zu tun. Der richtige Weg, dies zu schreiben, ist x ^ i + Alpha-Delta ^ {i, j} g_j, wobei Delta ^ {i, j} die inverse Metrik ist. Wenn man versucht, Optimierungstechniken auf einem Verteiler mit einer nicht trivialen Metrik zu definieren, muss man diese (inverse) Metrik berücksichtigen. Es gibt ein schönes Buch dazu:
http://sites.uclouvain.be/absil/amsbook/
und ein entsprechendes Matlab-Paket:
http://www.manopt.org

Am 22. Oktober 2014, um 22:52 Uhr, schrieb goretkin [email protected] :

Da wir über DFTs und Normen sprechen ... kam mir das heute in den Sinn

Wenn f eine Funktion eines Vektors ist und einen Skalar erzeugt, möchte ich, dass diff (f) eine Funktion ist, die einen Vektor akzeptiert und einen Covector erzeugt, so dass f (x) ~ = f (x0) + diff (f) ( x0) * (x-x0). Eine sehr häufige Verwendung des Gradienten besteht darin, eine lineare Annäherung der inkrementellen Änderung in der Ausgabe der Funktion bei einer inkrementellen Änderung in der Eingabe zu erhalten. Wenn die Eingabe ein Vektor ist, fühlt es sich natürlich an, dass der Gradient ein Covektor ist.

Wenn ich jedoch einen Gradientenabstieg implementieren würde, müsste ich den Gradienten bei x zu sich selbst hinzufügen (eine skalierte Version davon). In diesem Sinne ist der Gradient ein Vektor, der in die steilste Richtung zeigt, dessen Norm proportional zur Änderungsrate entlang dieser steilsten Richtung ist.

Mein Bauch sagt, Gradient ist ein Covector ist wichtiger.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an.

A_ (v'_w) ist eine Matrix
(A_v ') _ w erzeugt einen Fehler

Verdammt.

b2e4d59001f67400bbcc46e15be2bbc001f07bfe05c7c60a2f473b8dae6dd78a

Dieser Thread war überfällig für einen Beitrag mit einem humorvollen Bild.

@Jutho Ich gebe zu, dass ich nicht die allgemeinste Form des dualen Raums verwende, aber ich sehe nicht, wie meine Beschreibung überhaupt kreisförmig ist, da ich mich entschieden habe, einen dualen Raum im Rahmen eines Banach-Raums zu definieren. Der Banach-Raumformalismus ist für endlichdimensionale Vektorräume ohnehin schon übertrieben.

Rundschreiben ist in der Tat nicht der richtige Begriff. Was ich mit diesem Absatz sagen wollte, ist, dass ich diese Argumentation bezüglich einer effizienten Bewertung von Einträgen umkehren kann, da ich zum Beispiel im Fall des (konjugierten) Gradienten auf gekrümmten Verteilern den Gradienten (Covektor) g_i schätze

  1. Der duale Raum ist sicherlich ein Vektorraum, daher sind seine Elemente Vektoren. Das Ziel sollte jedoch nicht sein, dass alle Objekte, die die mathematische Bedeutung eines Vektors als Subtyp von Julias AbstractVector , noch alle Objekte, die Subtypen von AbstractVector sind, die richtigen mathematischen Eigenschaften haben eines Vektors. In diesem Sinne mag ich den Namen Matrix viel mehr als den Namen Vector , da er viel weniger Konnotation hat. Daher kann ich der Tatsache zustimmen, dass unabhängig davon, welcher Covector -Typ als Teil dieses Vorschlags eingeführt wird, es sich nicht um einen Subtyp von AbstractVector oder sogar AbstractArray selbst in den Fällen, in denen V * von Natur aus isomorph zu V ist (kartesischer Raum, der wahrscheinlich die Hälfte der Anwendungen ausmacht).
  2. Mit dem inneren Produkt können Sie eine Zuordnung von V zu V_ erstellen, dies ist jedoch keine Voraussetzung für die Existenz von V_. Dies klingt sicherlich nach einem irrelevanten Problem, da Sie beim Programmieren immer einen endlichdimensionalen Vektorraum haben und es immer ein inneres Produkt gibt (auch wenn es möglicherweise nicht das für die Anwendung relevante ist), aber den praktischen Punkt, den ich versucht habe machen ist das. Es gibt richtige Anwendungen von Covektoren in der Programmierung, wie das schöne Gradientenbeispiel von @goretkin , aber in den meisten Anwendungen, in denen Leute v' schreiben, versuchen sie nicht wirklich, einen Covektor zu konstruieren, sondern nur, einen bilinearen zu schreiben Abbildung zwischen zwei Vektoren (dh V x V auf Skalar), wie in v'_A_w = v ^ i A_ {i, j} w ^ j oder sogar v'w = v ^ i delta_ {i, j} w ^ j unter Verwendung von die bequeme Matrixdarstellung. Ich schreibe lieber dot(v,A*w) explizit, aber das ist eine persönliche Entscheidung. Da wir keine Informationen über obere oder untere Indizes auf Matrizen haben, kann man Anwendungsfälle konstruieren, in denen in einem skalaren Ausdruck wie v'*A*w sowohl v' als auch w entweder Vektoren oder Covektoren sein können im mathematischen Sinne. Die einzige Konsequenz daraus ist, dass ich nicht sicher bin, ob ich wirklich glücklich bin, den Typ v' Covector , da er genau wie der Name Vector zu viel Mathematik hat Konnotation, die in den meisten Anwendungen möglicherweise nicht gerechtfertigt ist, was dann zu weiteren (meist irrelevanten) Diskussionen wie diesen führt.

@jutho +1 für Gradienten als Covektoren
Ich habe dies zuerst aus Steve Smiths Doktorarbeit gelernt und diese Idee verwendet
um diese vage Idee des konjugierten Gradienten für Eigenwertberechnungen klar zu erklären
über die in der Chemie und Physik gesprochen wurde.

Ich bin zunehmend begeistert davon, wie intern konsistent
und in sich geschlossen ist die Welt der Tensoren des Ranges 2 mit einem oberen und einem unteren Index.
Dies ist das, was wir herkömmlicherweise Matrixberechnungen oder einfach nur alte lineare Algebra nennen.
Heck, während ich viele Beispiele wie Norm (v, p) oder fft (v) finden kann, wo Funktionen
unterscheiden, ob es sich um Vektoren oder Covektoren handelt, ich habe wirklich noch kein einziges gutes Beispiel (noch!)
einer Funktion, die sich auf einem Vektor und einer einspaltigen Matrix natürlich unterscheidet.
(Jemand hilft mir, sicherlich muss es einen geben, sogar viele !!)

Ich habe mich auch seit einigen Jahren wie @jutho um diesen abstrakten Vektor
Spaces hatten noch nicht den Weg nach Julia gefunden, ich erinnere mich, dass ich mit @StefanKarpinski chattete
darüber vor Jahren auf meinem Whiteboard. Immer noch die übergeordneten Bedenken von
1) Neulinge bei Julia müssen eine leichte Erfahrung haben und
2) Leistung
muss dieses schicke Zeug trumpfen.

Als ich mit @jiahao gesprochen habe, ist mir wirklich
Lineare Algebra (Arrays mit einem Contra und einem Co) und es gibt einfache Tensoren
(Jeder ist eine Co, keine Contras). Letzteres funktioniert gut in APL und Mathematica.
Ersteres befasst sich ausschließlich mit linearer Algebra und wurde zuvor am besten in MATLAB erfasst
Sie wurden auf Arrays mit einer Dimension von mehr als 2 gepfropft. Es gibt die umfassendere Allgemeinheit
was nach einem Wort mit @JeffBezanson so aussah, als wäre es nicht so verrückt.

Übrigens, ich denke, es hat ungefähr ein Jahr gedauert, wenn nicht mehr, aber wir nehmen das endlich ernst :-)

Hinsichtlich
A_ (v'_w) ist eine Matrix
(A_v ') _ w erzeugt einen Fehler

Nur die Matrixmultiplikation "*" ist assoziativ. Die überladene Skalarzeitmatrix war nie
assoziativ. Nicht einmal in Mathe, nicht einmal in MATLAB.

A_ (v'_w) ist eine Matrix
(A_v ') _ w erzeugt einen Fehler

Guter Fang. Gibt es jedoch Fälle, in denen Nichtfehler zu unterschiedlichen Antworten führen? Eine verwandte Frage: Was soll ein Scalar * Covector tun?

Die überladene Skalarzeitmatrix war niemals assoziativ. Nicht einmal in Mathe, nicht einmal in MATLAB.

Operationen, die nur Matrizen und Skalare betreffen, sind assoziativ (siehe Punkt 3). Da wir Vektoren als 1-Spalten-Arrays behandeln können, ist das Einschließen dieser Vektoren ebenfalls kein Problem. Die Falte hier ist, dass der Covector ein Operator ist, der Dimensionen reduzieren kann (Vektoren auf Skalare abbilden), daher bin ich mir nicht sicher, wie das passt.

Die Hinzufügung von Skalarmatrix ist ein größeres Problem, da dies die Verteilbarkeit ruiniert (obwohl ich denke, wenn ich mich richtig erinnere, dass die Debatte beschlossen hat, sie beizubehalten):

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

Dies ist eine sehr schöne Zusammenfassung:

Als ich mit @jiahao gesprochen habe, ist mir wirklich
Lineare Algebra (Arrays mit einem Contra und einem Co) und es gibt einfache Tensoren
(Jeder ist eine Co, keine Contras).

Es ist klar, dass es in dieser Diskussion nicht darum geht, den allgemeineren Fall zu unterstützen. Das ist zu verwirrend für Leute, die nicht die vollständige Allgemeinheit benötigen, nicht in die aktuelle AbstractArray-Hierarchie aufgenommen werden können und besser für ein Paket geeignet sind (an dem ich gerade arbeite) auf). Aber die Diskussion ist in der Tat, welche dieser beiden besonderen Welten wir unterstützen wollen, da sie nicht miteinander kompatibel sind.

Im ersteren sind alle Vektoren v Spalten, alle v' sind Covektoren und alle Matrizen sind lineare Operatoren V-> W (z. B. leben sie in W ⊗ V_). Dies schließt die Darstellung von z. B. bilinear aus bildet V x V -> Skalar ab (z. B. v ^ i A_ {i, j} w ^ j), da dies eine Matrix mit zwei niedrigeren Indizes erfordert (z. B. Leben in V_ ⊗ W_). Natürlich können Sie es in der Praxis weiterhin verwenden und als v'_A*w schreiben, aber es steht in Konflikt mit der gewählten Nomenklatur der Objekte. Außerdem wird nicht angegeben, in welchem ​​Raum Arrays höherer Ordnung leben.

Der letztere Fall behebt dieses Problem, da es (V == V *) hat, führt jedoch zu überraschenden Ergebnissen als v' == v . Darüber hinaus ist diese Lösung tatsächlich auf reale Vektorräume beschränkt, da selbst in komplexen Räumen mit einem euklidischen Standardinnenprodukt (dh einem „komplexen kartesischen Raum“) der duale Raum von Natur aus nicht zu V isomorph ist, sondern zu konj (V) (V. bar), der konjugierte Vektorraum (siehe Hilbert-Räume in http://en.wikipedia.org/wiki/Complex_conjugate_vector_space)

In Bezug auf die Nichtassoziativität ist in dieser Hinsicht das aktuelle Verhalten, bei dem v'*v keinen Skalar, sondern ein Array erzeugt, tatsächlich konsistenter, da seitdem sowohl A*(v'*v) als auch (A*v')*v einen Fehler erzeugen.

Die "lineare Algebra" -Seite der Dinge kann zusätzlich in verschiedene Möglichkeiten der Darstellung von Vektoren und Covektoren aufgelöst werden:

  • Der Covector, auch bekannt als "lustiger Zeilenvektor" -Vorschlag, den wir kürzlich besprochen haben.
  • Die Semantik "keine wahren Vektoren", die zusammenfließt (N-Vektoren als Nx1-Matrizen) (N-Zeilenvektoren als 1xN-Matrizen), wird von Matlab und dergleichen unterstützt.
  • Der aktuelle Julia-Weg - nicht dichte N-Vektoren und Nx1-Matrizen zusammenführen, spärliche N-Vektoren und Nx1-Matrizen zusammenführen, N-Zeilenvektoren als 1xN-Matrizen darstellen.

Ich frage mich, ob es unbedingt notwendig ist, auch (Skalare, 1-Vektoren und 1x1-Matrizen) in der Welt der "keine wahren Vektoren" zusammenzuführen. @alanedelman und ich haben gestern darüber gesprochen und in der numerischen linearen Algebra wird die Kommutativität von Vektor * Skalar und Skalar * Vektor überall verwendet, aber das Vektor * Skalarprodukt ist dasjenige, das sich nicht darum kümmert, ob es (N,) * ( ,) oder (N, 1) * (1,1).

1) Letztendlich sind die wichtigsten Dinge A) Benutzerfreundlichkeit und B) Leistung.
2) Die beiden Welten sollten in völliger Harmonie nebeneinander existieren können. Bei technischen Arbeiten gibt es meiner Meinung nach nichts Intuitiveres und Einfacheres als die Tensornotation. Ein Vorschlag, wenn ich darf, könnte sein, ein Umgebungsflag zu aktivieren, oder ein Paket könnte zu Beginn eines Programms importiert werden. Dies würde eine einfache Verwendung und eine einfache Programmierlogik ermöglichen. Ist das nicht möglich oder müssen wir ein übergeordnetes Schema auswählen?

Es scheint zwei Möglichkeiten zu geben
1) Aktivieren Sie den gleichzeitigen Import einer Toolbox, eines Patches oder eines Umgebungsflags
2) Erstellen Sie eine Sprache, die in der Lage ist, die besonderen Welten zu vereinen, aber dennoch einfach zu bedienen und schnell ist

Ich bin mir nicht sicher, wie vielversprechend dies ist, aber ich bin gerade auf ein potenziell interessantes Forschungsgebiet gestoßen.

Darf ich Ihre Aufmerksamkeit auf Folgendes lenken:

Forschungsgruppe Geometrische Algebra
http://www.mrao.cam.ac.uk/~clifford/pages/introduction.htm

Vorlesung in Geometrischer Algebra
http://www.mrao.cam.ac.uk/~clifford/ptIIIcourse/course99/

Physikalische Anwendungen der geometrischen Algebra
http://www.mrao.cam.ac.uk/~clifford/ptIIIcourse/

Eine einheitliche mathematische Sprache für Physik und Ingenieurwesen im 21. Jahrhundert
http://www.mrao.cam.ac.uk/%7Eclifford/publications/ps/dll_millen.pdf

Geometrische Algebra
http://arxiv.org/pdf/1205.5935v1.pdf

Grundlagen des geometrischen Algebra-Computing
http://link.springer.com/book/10.1007%2F978-3-642-31794-1

Geometrisches Algebra-Computing
http://link.springer.com/book/10.1007%2F978-1-84996-108-0

Nachdem ich mir das etwas genauer angesehen habe, scheint es wirklich erstaunlich.

Solange Algebra und Geometrie getrennt wurden, waren ihre Fortschritte langsam und ihre Verwendung begrenzt. aber wenn diese beiden Wissenschaften vereint sind, haben sie sich gegenseitig Kräfte verliehen und sind gemeinsam zur Vollkommenheit marschiert.

  • Joseph Louis Lagrange

Stellen Sie sich vor, Sie brauchen eine Brille und wissen nicht, dass Sie eine Brille brauchen. Wenn Sie dann eine Brille bekommen, ändert sich die Welt unerwartet. GA ist wie eine Brille für das Innere Ihres Gehirns.

  • Pablo Colapinto

Dieser Typ scheint die richtige Idee zu haben ... https://github.com/wolftype/versor

Typische Matrixoperationsbibliotheken enthalten Inline-Funktionen für die Vektor- und Matrixmultiplikation. Die geometrische Algebra kombiniert viele andere mathematische Elemente (Matrix-, Tensor-, Vektor- und Lügenalgebren). Versor ist ähnlich, aber bei Steroiden, bei denen Vektoren und spärliche Matrizen unterschiedlicher Größe nur als Multivektoren bezeichnet werden und geometrische Elemente darstellen, die über xyz-Richtungen und Transformationsmatrizen hinausgehen. Kreise, Linien, Kugeln, Ebenen und Punkte sind alle algebraische Elemente, ebenso wie Operatoren, die diese Variablen drehen, verdrehen, erweitern und biegen. Sowohl diese Elemente als auch Operatoren sind Multivektoren, die sich auf viele, viele verschiedene Arten miteinander multiplizieren.

Dieses Video enthält einige Details zu Versor, der programmierten GA-Mathematiksprache.
https://www.youtube.com/watch?v=W4p-e-g37tg

Dies sind die Folien dafür. https://github.com/boostcon/cppnow_presentations_2014/blob/master/files/generic_spaces.pdf

Sie können einige erstaunliche Tensorberechnungen durchführen, die sehr einfach und beim Kompilieren auf Geschwindigkeit optimiert sind.

@ esd100 , ich denke, es wäre hilfreich, die Diskussion über diesen Thread auf das spezifische Thema im Titel zu konzentrieren.

@johnmyleswhite Ich habe nach dem Begriff "Tensor" gesucht und er wurde 171 Mal erwähnt (jetzt 172 Mal). Obwohl ich Ihrer Aussage zustimme, enthält dieser Thread im Allgemeinen 157 Kommentare (jetzt 158), von denen sich einige direkt und indirekt mit dem Titel des ursprünglichen Beitrags befassen (Vektortranspositionen ernst nehmen). Ich denke, mein Beitrag befasst sich indirekt mit dem Titel des ursprünglichen Beitrags, indem er eine bessere Perspektive darüber bietet, was mit neuen Anwendungen der Tensormathematik über geometrische Algebra getan werden kann. Meiner Meinung nach wäre es ein zusätzliches nützliches Merkmal von Julia, die höherdimensionale Kraft, die Versor in Julia hat, einzubauen. Der Titel des youTube-Videos lautete "Generische Programmierung generischer Räume: Geometrische Algebra zur Kompilierungszeit mit C ++ 11". Ich verstehe nicht, warum das nicht Julia statt C ++ sein kann. Andererseits bin ich kein Mathematiker oder Informatiker.

@ esd100 Geometrische Algebren sind interessant, aber nicht die allgemeinste Sache, die in dieser Ausgabe behandelt wird. Die geometrische Algebra, die uns in erster Linie interessieren würde, ist die reale Clifford-Algebra Cl (R ^ n, I); Wir wären jedoch auch an anderen Clifford-Algebren interessiert, die keine geometrischen Algebren sind, wie Clifford-Algebren wie über komplexen Vektoren Cl (C ^ n, I) oder sogar Algebren über beliebigen Feldern oder nichtkommutativen Ringen Cl (F ^ n, I). . Darüber hinaus möchten wir uns nicht einmal unbedingt auf Clifford-Algebren beschränken, sondern darüber nachdenken, wie sich die lineare Algebra auf die allgemeinere Einstellung der Tensoralgebra verallgemeinert, bei der quadratische Formen (innere Produkte) nicht unbedingt definiert sind.

In der Einstellung der Tensoralgebra ist der Vorschlag " v' ist ein No-Op" im OP absolut sinnvoll, da er konsistent auf den Umkehrantiautomorphismus verallgemeinert wird. Dies ist jedoch nur einer von mindestens drei Vorschlägen auf dem Tisch. Man könnte auch bialgebraische Erweiterungen in Betracht ziehen, die zu Tensorkohlegebren führen, bei denen der Vorschlag "v 'erzeugt einen Covektor" sehr natürlich ist. Ich bin mir nicht sicher, wie sich der Vorschlag "kein wahrer Vektor" zu einer Tensoralgebra verallgemeinert.

@jiahao Danke für deine sehr informative Antwort. Es ist schön, wenn jemand, der das Thema beherrscht, etwas Licht ins Dunkel bringt. Ich werde mich mehr mit diesen Themen befassen, um ein besseres Verständnis zu erhalten. Ich bin gespannt, warum es superoptimierten Code für BLAS gibt, aber superoptimierten Code für komplexere Algebren, wie es Clifford Algebra nicht tut.

Ich bin mir nicht sicher, ob diese Diskussion noch andauert, aber ich wollte meine Meinung teilen, da diese Themen einige der wichtigsten (und nervigsten) Teile meiner Verwendung von Julia sind. Einige der oben genannten Punkte stimme ich zu, andere nicht.

Grundsätzlich denke ich, dass wir dies erkennen müssen: Alle Julia-Benutzer möchten schnelle, mehrdimensionale Arrays (zur Datenspeicherung), wie sie bereits in Julia implementiert sind. Viele / die meisten Benutzer werden auch an linearer Algebra interessiert sein, und die oben genannten Arrays sind eine bequeme Möglichkeit, die in Vektoren und Matrizen enthaltenen Daten zu verpacken. Einige Benutzer möchten eine generische Tensoralgebra (multilineare Algebra) durchführen.

Die Frage ist, wie diese "nackten" Arrays auf die mathematischen Konzepte der linearen Algebra erweitert werden können. Wie lässt du die Syntax wie normale Mathematik aussehen? Werden sich die Leute wohl fühlen, dass v ''! = V? Usw. usw.

Das erste ist, wir wollen Arrays nicht schlechter machen, nur damit wir lineare Algebra machen können. Wir möchten Vector keine zusätzlichen Daten oder Array unnötige Vorlagenargumente hinzufügen. Wir wollen so schnell wie C / C ++ sein, wenn wir keine lineare Algebra machen (und hoffentlich so schnell wie C / fortran, wenn wir es sind).

Das zweite, was wir alle erkennen sollten, ist, dass eine vollständige mehrdimensionale Tensorkontraktion erhebliche Mengen zusätzlicher Informationen erfordert. Schreiben Sie für bis zu zweidimensionale Tensoren eine beliebige Multiplikation wie A_B'_C * D ..., während es für allgemeine Tensoren natürlicher ist, sich ein Diagramm zur Definition der Kontraktion vorzustellen (ein Tensornetzwerkdiagramm oder ein Faktordiagramm - das Gleiche gilt mit vielen verschiedenen Namen). Für hochdimensionale Tensoren ist es sinnvoll, die bloßen Arrays einzuwickeln und mit Vektorräumen usw. zu dekorieren, um alles im Auge zu behalten. Dies kann in Paketen erfolgen, an denen Jutho (und möglicherweise andere) arbeiten. Es macht keinen Sinn, die Bedeutung von 'auf 3+ dimensionalen Tensoren zu betrachten - niemand wäre daran interessiert, diese Funktion zu verwenden, wenn permutierte Tensoren existieren oder wenn sie ein spezielles Tensorpaket verwenden.

Die Hauptbenutzer müssen Matrizen multiplizieren, Vektoren hinzufügen und so weiter. Sie werden Matrizen transponieren wollen. Sie werden auch ein inneres und ein äußeres Produkt wollen. Ob es Ihnen gefällt oder nicht, diese werden in Verbindung mit einem dualen Raum definiert. Wir wissen, was dies für reelle und komplexe Zahlen ist, und haben sie vordefiniert. Und ja, es ist wahr, dass sich der duale Raum eines realen endlichen Vektorraums im Grunde genommen genauso anfühlt, und ich glaube, das ist es, was das ganze Problem trübt. Das größte aktuelle Problem ist, dass wir keinen richtigen Doppelvektor haben. Ohne sie können wir in Julia keine Gleichungen wie auf Stift und Papier "schreiben" - ein großes Problem! Noch wichtiger ist, dass wir nicht v '' = v haben. Dies ist sehr unintuitiv.

Menschen werden nur einen doppelten Vektor für die Ausführung innerer oder äußerer Produkte erstellen wollen oder müssen. Ein einfacher dekorativer Wrapper, der dem Compiler mitteilt, was zu tun ist, wenn er das nächste Mal auf * trifft, ist eine sinnvolle Lösung und sollte keinen Laufzeitaufwand haben. Ich denke, diese Doppelvektoren / Covektoren sollten indexierbar, addierbar / subtrahierbar, multipliziert mit einem Skalar, multipliziert mit einem Vektor (Rückgabe eines Skalars) und multipliziert mit einer Matrix (Rückgabe eines Covektors) sein - und das war's auch schon! Ich würde nicht einmal explizit die komplexe Konjugation für komplexe Vektoren durchführen - die in das Punktprodukt, das äußere Produkt, die Indizierung usw. eingebaut werden könnte, um die Geschwindigkeit / den Speicher zu verbessern.

Ich bin nicht besorgt, dass wir dem Typensystem Komplikationen hinzufügen. Ich glaube, dass dies notwendig ist, um Mathematik so zu verkapseln, wie es die Benutzer an der High School und an der Universität gelernt haben: assoziativ * (wo genau definiert), v '' = v, sowohl "BHs" als auch "Kets" (I. verwende hier die Dirac-Notation der linearen Algebra usw.

Übrigens, wenn dieses Zeug in Julia 0.4 implementiert wurde, bin ich mir dessen nicht bewusst! Ich arbeite immer noch daran, 0.3.4 zu verstehen ...

@andyferris , ich stimme dem im Allgemeinen zu. Ich denke, dass mein Vorschlag hier leicht geändert M*v' ein Fehler zu sein, lassen Sie ihn einen Covektor erzeugen und statt v*M einen Fehler, lassen Sie ihn einen Vektor erzeugen. Für mich bedeutet dies konzeptionell, dass M unabhängig davon ist, ob die Abmessungen höher oder niedriger sind - Sie können v'*M*w oder v*M*w' für ein inneres Produkt schreiben, und beide funktionieren.

Ein Gedanke, der sich herumgesprochen hat, ist, dass Row-Major- und Col-Major-Arrays und M.' einfach von einem zum anderen wechseln und in ähnlicher Weise v' die Row-Major-Variante von v - hat die gleiche Form, wird aber konzeptionell in der anderen Reihenfolge gespeichert. Ich bin mir jedoch nicht sicher.

+1 bis @andyferris . Ich denke auch, wir wollen nur einfache Wrapper-Typen Transpose und ConjTranspose , mit denen wir die prägnante Matrixalgebra-Syntax zum Schreiben von Produkten aus Vektoren, Matrizen und Transponierungen davon verwenden können. In Bezug auf Punkt 2 des Vorschlags von @StefanKarpinski würde ich diese Wrapper nicht auf einen Container mit AbstractArray Objekten beschränken und die Typen nicht Teil der AbstractArray Typhierarchie selbst machen. Transpose(x) sollte nur der Typ sein, der sich aus dem Schreiben von x' in einen Ausdruck ergibt und es ermöglicht, seine Bewertung zu verschieben / eine verzögerte Bewertung zu haben, abhängig vom Rest des Ausdrucks, indem auf Transpose für den Rest der Operationen im Ausdruck (der wahrscheinlich in 99,9% der Fälle der Multiplikationsoperator * wird). Benutzer sollten jedoch in der Lage sein, dieser Syntax auch eine Bedeutung für neue Typen zu geben, die möglicherweise nicht Teil der AbstractArray -Hierarchie sind, wie z. B. die Matrixfaktorisierungsobjekte oder andere Typen zum Definieren von z. B. linearen Operatoren.

Für den speziellen Fall von Matrizen und Vektoren sehe ich den Anwendungsfall für M*v' nicht wirklich, aber ich bin nicht grundsätzlich dagegen. Wichtig ist, dass es die grundlegenden Erwartungen erfüllt (dh Regeln 2,4,5,6 und 8 des Endes von Stefans Vorschlag ).

Der letzte Hauptdiskussionspunkt ist wahrscheinlich das Zusammenspiel mit dem Schneiden. Sollte ein Zeilenabschnitt einer Matrix automatisch ein Transpose ? Ich stimme hier über APL-Schnittregeln ab, bei denen dies nicht der Fall ist.

@Jutho , die Motivation besteht darin, * assoziativ zu machen - A*(v'w) und (A*v')*w funktionieren beide und führen zu denselben Ergebnissen, Modulo-Nichtassoziativität des zugrunde liegenden Elementtyps (z. Punkt).

Damit (A*v')*w das gleiche Ergebnis wie A*(v'*w) A*v' müsste N=3 -Array sein. Hast du das gedacht? Ich habe das komplett vermisst.

OK, sehr interessant, ich habe ein paar Kommentare.

Zuerst müssen wir die Hauptbenutzer ansprechen. Grundsätzlich ist die Verwendung von Array{T,n} als n -dimensionaler Speicher in erster Linie die Hauptfunktion. Dies muss für Programmierer sinnvoll sein, die keine Ahnung von linearer Algebra haben, aber Daten in Julia manipulieren möchten. Aus diesem Grund kann ein Array-Slice unter keinen Umständen einen Co-Vektor zurückgeben! Dies ist ein streng lineares Algebra-Konzept, das nur für bestimmte Datentypen T , die einen Vektorraum und sein Dual definieren können (z. B. numerische Daten oder "Felder").

Ich könnte mit APL Slicing in beide Richtungen gehen. Es scheint intuitiv genug. Es ist typstabil und macht nichts Überraschendes. Obwohl ich nicht denke, dass es notwendig ist, diese Änderung vorzunehmen, um die lineare Algebra selbstkonsistent zu machen ... könnte es schön sein (obwohl es eine bahnbrechende Änderung sein könnte). Ich finde es beunruhigend, dass M[1,:] Größe 1xn und M[:,1] Größe n (nicht nx1) ist ... es scheint zu asymmetrisch ... aber ich denke, es ist eine gute Erinnerung daran, wie die Dinge sind in Erinnerung gelegt. Auf jeden Fall sollte diese Änderung nur vorgenommen werden, wenn sie für die Speicherung und Manipulation abstrakter Daten sinnvoll ist. Für mich ist diese Änderung orthogonal zur Diskussion über die lineare Algebra.

Selbst wenn wir keine APL-Slicing-Regeln implementieren, können wir dennoch ganz einfach eine aussagekräftige lineare Algebra retten. Wenn Sie den i -ten Spaltenvektor von M möchten, rufen Sie colvec(M,i) und wenn Sie den i -ten Zeilen- Co- Vektor von M möchten rowvec(M,i) . Wenn i ein Tupel oder Vektor wäre, könnte es ein Tupel oder einen Vektor oder (Co-) Vektoren zurückgeben (könnte dies in einigen Fällen nützlich sein, um über Parallelisierung nachzudenken?). Wenn Sie eine Matrix bevorzugen, verwenden Sie einfach die normale Indexnotation. Wenn wir APL-Slicing-Regeln verwenden, ist dasselbe sehr nützlich, um die Aktion von Zeilen- und Spalten-Slices mit dem Symbol * . (Man muss überlegen, wie sich die komplexe Konjugation mit rowvec verhält).

Auf diese Weise ist bei linearer Algebra alles sinnvoll und der Benutzer muss sich keine Gedanken darüber machen, welche bestimmte Slicing-Regel Julia implementiert. Der Operator ' würde nur auf Vektoren und Covektoren (um die Dekoration zu ändern) und auf Matrizen (entweder direkt oder faul) einwirken. Es kann einfacher sein, sich daran zu erinnern, wie man Basisvektoren aus einer Eigendekomposition extrahiert, die ich immer zu vergessen scheine.

Für * denke ich, dass die Matrix- / Vektoralgebra in Julia funktionieren sollte, damit sie wie Mathematik als Multiplikationsoperator aussieht und sich anfühlt und niemals wie der Tensorproduktoperator zwischen zwei Vektoren, zwei Covektoren, zwei Matrizen usw. Wir sollte M*v' nicht zulassen, da Sie in der Mathematik die Gleichung ausschließlich mit dem Symbol "\ otimes" (dem Zeitzeichen innerhalb eines Kreises) und nicht mit dem Standard-Multiplikationssymbol schreiben müssen. Mit dem Multiplikationssymbol ist die Bedeutung schlecht definiert! Wir können nicht w*M*v' == v'*M*w weil die Matrixmultiplikation nicht kommutativ ist. Auch für M*v'*v es nicht sinnvoll, über die Assoziativität von * zu sprechen. Sie können dies entweder als innerproduct(outerproduct(M,v'),v) interpretieren, M * innerproduct(v',v) bei der es sinnvoll ist, * für beide zu verwenden. Mit diesem Ausdruck benötigen wir möglicherweise eine Klammerung, oder der Compiler könnte nach einer sinnvollen Reihenfolge von Operationen suchen (beachten Sie hier, dass die schnellste Reihenfolge der Bewertung auch die meiner Meinung nach einzig gültige Reihenfolge der Bewertung ist).

Mit Vektoren, Covektoren und Matrizen können wir ein algebraisches System haben, das mit der Standardmathematik übereinstimmt. Wann immer Sie das äußere Produkt zwischen zwei Vektoren oder zwei Covektoren oder zwei Matrizen ausführen möchten, wechseln Sie von Natur aus zur multi-linearen Algebra oder zur generischen Tensoralgebra. Hier könnten Sie möglicherweise Matrizen und Vektoren haben, die sich wie kron(M,N) mit einem neuen Symbol multiplizieren. Oder Sie können von Benutzern verlangen, dass sie ein vollständiges multi-lineares Algebra-Paket verwenden. Oder was auch immer ... es scheint jenseits des Rahmens der ursprünglichen Frage (die vor 14 Monaten war, übrigens ...)

Zusammenfassend sehe ich hier vier fast völlig unterschiedliche Dinge:

  1. Verbessern Sie das Slicing von Array s beliebiger Daten mithilfe von APL-Slicing-Regeln.
  2. Machen Sie die lineare Algebra in Julia intuitiver und konsistenter mit der Mathematik, indem Sie einige einfache Konzepte hinzufügen: Covektoren und eine Methode zum Extrahieren von Vektoren und Covektoren aus Matrizen sowie Operationen zwischen Covektoren, Matrizen usw. Viele Benutzer bemerken möglicherweise nicht einmal, dass Covektoren existieren, da Die Verwendung von 1xn- und nx1-Matrizen funktioniert weiterhin wie erwartet, und der Vektor verhält sich gleich.
  3. Implementieren Sie aus Gründen der Geschwindigkeit eine verzögerte Auswertung für transpose , conj usw., indem Sie Wrapping-Typen ähnlich wie Covector verwenden, aber auch für Matrizen.
  4. Entwickeln Sie ein Allzweck-Tensorpaket und fügen Sie es möglicherweise der Standardbibliothek hinzu.

Möglicherweise wird nur die erste eine bahnbrechende Veränderung sein? Die anderen verbessern oder erweitern die aktuelle Funktionalität. Sie können alle mehr oder weniger unabhängig voneinander implementiert werden, und sie sind wahrscheinlich alle lohnende Dinge zu tun. (PS: Wenn Sie APL-Slicing möchten, empfehle ich, dies bald zu tun, bevor Julia zu "groß" wird und zu viele Pakete usw. zerstört werden.)

Ja, tut mir leid, mein Vorschlag, M*v' zuzulassen, macht keinen Sinn, da die verschiedenen Arten der Zuordnung von Produkten völlig unterschiedliche Formen erzeugen. Ich denke, wenn wir dies ändern wollen, ist mein ursprünglicher Vorschlag der richtige Weg. Bisher scheint dies die beste Kombination mit dem APL-Slicing-Verhalten zu sein. Ob wir ein APL-Slicing-Verhalten wollen oder nicht, ist natürlich eine übergeordnete Überlegung.

Ich wollte nur ein paar Dinge sagen und bitte nimm sie mit einem Körnchen Salz. Sie sind nur eine Meinung und ich weiß, dass meine nicht viel Gewicht haben. Eines fiel mir jedoch auf, als ich den Kommentar von @andyferris las. Ich glaube nicht, dass ich dem Kommentar zustimme, dass es für Programmierer sinnvoll sein muss, die keine Ahnung von linearer Algebra haben, aber Daten in Julia manipulieren möchten. Ich denke nicht, dass Programmierer das Publikum sind, für das mathematische Programme geschrieben werden sollten. Wenn ein Programmierer Daten bearbeiten möchte, gibt es viele Sprachen, die dies ermöglichen. Julia ist eine Sprache für das Rechnen und sollte anspruchsvolles technisches Rechnen ermöglichen.

@ esd100 Stimmt, daran habe ich gedacht. Aber mit Julia dachte ich, wir wollten großartig sein ... wollten vielen Zwecken gerecht werden und uns in vielen Dingen auszeichnen. Es kann echte wissenschaftliche Gründe für die Manipulation nicht numerischer Daten geben. Es kann aktuelle oder ehemalige Wissenschaftler geben, die Julia-Benutzer sind und allgemeinere Programmierungen durchführen möchten. Mein früherer Punkt war, dass wir Array langsamer machen oder mehr Speicherplatz beanspruchen sollten, indem wir ihm ein zusätzliches Feld geben, um über Transponierung / Konjugation zu sprechen.

Wenn ein APL-Slicing durchgeführt wird, sollten entweder Zeilen- oder Spalten-Slices dasselbe Objekt zurückgeben. Die Frage war: Ohne APL-Slicing, was ist das Beste für M[1,:] um zurückzukehren? Nun, wenn M[1,:,:,:,:] ein Array{T,n} M[1,:,:,:,:] zurückgibt, würde es jeden verwirren, wenn M[1,:] ein covector{T} zurückgibt. Was ist, wenn wir M = zeros(3,3,3) und M[1,:] aufrufen, was derzeit eine 1x9-Matrix zurückgibt? Sollten wir dies auch zu einem Covector machen? Was wäre, wenn M Array{String,3} ?

Das scheint mir ziemlich überraschend und verwirrend. Es ist auch nicht konsistent mit APL-Slicing-Änderungen, falls dies in Zukunft passieren sollte. Daher schlug ich vor, neue Funktionen für lineare Algebra-Zwecke hinzuzufügen, rowvec(M,i) und colvec(M,i) . Es ist nicht ideal, es fügt neue Funktionen hinzu ... aber zumindest ist klar, was sie für lineare Algebra-Zwecke zurückgeben sollen. Es war das einzige, was ich mir vorstellen konnte, dass es gute Eigenschaften für generische Arrays und gute Eigenschaften für die lineare Algebra (zusammen mit einem Covektortyp) hatte, ohne zu versuchen, die multi-lineare Algebra zu berücksichtigen. Wenn jemand eine schönere Notation zum Extrahieren von Covektoren aus Matrizen hat, wäre das auch schön!

@ esd100 : Ich bin mir ziemlich sicher, dass Programmierer das Publikum sind, für das Julia entwickelt wurde. Wissenschaftliches Rechnen ist Julias Stärke, aber es gibt viele Julia-Benutzer, die es nur als allgemeine Programmiersprache verwenden.

Ich stimme @andyferris zu, dass man die Tensoranforderungen von den Containeranforderungen

Ohne den gesamten Thread gelesen zu haben, ist mein persönliches Problem, dass wenn ich ein 3D-Array A (z. B. tomografische Daten) habe und dies tue

imagesc( data[1,:,:] )

Das funktioniert nicht. IMHO data[1,:,:] , data[:,1,:] und data[:,:,1] sollten 2D-Arrays (oder Subarrays) sein. Derzeit verwende ich eine selbst definierte squeeze -Funktion, um dieses Problem zu beheben.

Auch dies ist nur meine Meinung und eine sehr unwichtige, da ich weit von der Aktion entfernt bin. Ich denke, wenn eine Anwendung entwickelt wird, sollte sie eine Reihe von Designprinzipien haben, die sie leiten. Die Bauherren müssen eine klare, einheitliche Nachricht über ihren Zweck und ihre Identität senden. Wenn die Entwicklung einer schnellen, intuitiven, hoch entwickelten und technischen Computerplattform das Ziel ist und das Julias Stärke ist, bleiben Sie dabei. Senden Sie keine gemischten Signale, indem Sie versuchen, den Zweck dem Publikum allgemeiner Programmierer anzupassen.

Der Punkt ist, dass es selbst für rein wissenschaftliche oder mathematische Anwendungen mehrere widersprüchliche Interpretationen gibt, z. B. mehrere mathematische Objekte, die Sie mithilfe von Vektoren und Matrizen darstellen können, und daher sollte man keine zu spezifischen Entscheidungen treffen.

Es ist besser, spezielle Pakete zu haben, um die Anforderungen bestimmter Disziplinen nach bestimmten Konventionen zu erfüllen.

@jutho Mein Bauchgefühl ist, dass Sie Recht haben, aber ich frage mich, auf welcher Grundlage Sie zu dem Schluss kommen, dass Pakete "besser" sind und im Vergleich zu welchen Alternativen?

Das ist ganz einfach. Funktionen, die für die meisten Julia-Benutzer wichtig sind, gehören zu Base. Eine speziellere Funktionalität gehört in ein Paket.

Natürlich ist es hier nicht einfach, eine Linie zu ziehen. Der beste Weg, um etwas in die Basis zu bringen, besteht darin, ein Paket zu erstellen, damit viele Leute dies testen können. Verschiedene Kernfunktionen, die in Base gelandet sind, wurden zuerst in einem Paket entwickelt.

@ esd100 , ich verstehe deine Frage nicht ganz. Am Ende sollte eine wissenschaftliche Sprache die Datenstrukturen und Methoden zur Darstellung und Berechnung typischer in der Wissenschaft verwendeter Objekte bereitstellen. Bestimmte Datenstrukturen können jedoch nützlich sein, um unterschiedliche mathematische Strukturen darzustellen, und manchmal kann eine unterschiedliche Darstellung für Objekte desselben Typs zweckmäßig sein. Daher kann das Zuordnen einer festen mathematischen Struktur zu einem Datentyp für einige Disziplinen zu restriktiv und für andere Disziplinen zu komplex sein. Dies sollte also nicht in der Julia-Basis verfolgt werden, sondern durch spezifische Pakete, die nur versuchen, die Bedürfnisse einer Disziplin zu erfüllen.

In Bezug auf die aktuelle Diskussion wird jeder, der mit Vektoren und Matrizen arbeitet, die Möglichkeit erwarten, einen Vektor zu transponieren und v ^ T * w = Skalar zu haben. Diese Vektoren und Matrizen können jedoch verwendet werden, um eine Reihe verschiedener Dinge darzustellen, und dies hängt wahrscheinlich vom Fachgebiet / der Disziplin ab. Ich habe Beispiele dafür gegeben, wo v ^ T in den obigen Beiträgen möglicherweise kein tatsächlicher Covector ist.

Am 11. Januar 2015, um 18:10 Uhr, schrieb esd100 [email protected] :

@jutho https://github.com/jutho Mein Bauchgefühl ist, dass Sie Recht haben, aber ich frage mich, auf welcher Grundlage Sie zu dem Schluss kommen, dass Pakete "besser" sind und im Vergleich zu welchen Alternativen?

- -
Antworten Sie direkt auf diese E-Mail oder sehen Sie sie auf GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -69501771 an.

Das ist ganz einfach. Funktionen, die für die meisten Julia-Benutzer wichtig sind, gehören zu Base. Eine speziellere Funktionalität gehört in ein Paket.

Ich denke nicht, dass es so einfach ist. Zum Beispiel gibt es in Base Bessel-Funktionen, und es ist höchst unwahrscheinlich, dass sie für die meisten Julia-Benutzer wichtig sind. Der Grund, warum sie in Base sein können, ist, dass es einen einigermaßen universellen Standard für die Bessel-Funktionen gibt und niemand diese Namen wahrscheinlich für etwas anderes verwendet oder erwartet, dass sie etwas anderes tun.

Durch sorgfältige (manche sagen wortreiche) Namenskonventionen kann Mathematica über 4000 Funktionen in die Kernsprache einfügen, und ich finde es äußerst praktisch, fast nie ein Paket laden zu müssen. Im Gegensatz dazu ist es bei Verwendung von Python / Sage nicht ungewöhnlich, dass eine Datei / ein Notizbuch oben explizit 200 Funktionen importiert (wobei die nicht nachvollziehbare Syntax "von ___ import *" vermieden wird). Jedes Mal, wenn ich eine Funktion verwenden muss, die nicht integriert ist, muss ich einige zusätzliche Schritte ausführen:

(1) Versuchen Sie sich zu erinnern, ob ich es bereits in dieser Datei verwendet habe, und / oder führen Sie eine Textsuche durch, um dies zu bestätigen (Beschränkung auf ganze Wörter, wenn der Name eine Teilzeichenfolge von etwas anderem ist).
(2) Entweder: (a) den Import sofort hinzufügen, meinen Gedankengang verlieren und ihn wiederfinden; oder (b) versuchen Sie sich daran zu erinnern, den Import hinzuzufügen, nachdem ich alles getan habe, was ich getan habe, und eine andere Sache im Speicher zu behalten
(3) Für weniger gebräuchliche Funktionen muss möglicherweise nachgeschlagen werden, in welchem ​​Paket es sich befindet

Dies kann ärgerlich und schwächend werden. Daher denke ich, dass wie bei den Bessel-Funktionen alles, was als Standard angesehen wird und daher keine Namenskonflikte oder Verwirrung verursacht, in Base sein sollte, unabhängig davon, ob viele Leute es verwenden. Sicherlich lineare Algebra.


Zurück zum Thema, wir haben über mehrere Funktionalitätsebenen gesprochen - von Arrays als semantisch nackte Container (wie in Base.AbstractArray implementiert) bis zu kartesischen Tensorobjekten mit Auf- / Ab-Semantik (wie von my beschrieben) AbstractTensorArray Vorschlag für allgemeinere Tensorobjekte mit Indizes, die auf Vektorräume abgebildet sind (wie in TensorToolbox ) - mit zunehmender Allgemeinheit und abnehmendem Optimierungspotential.

Ich denke, es ist wichtig, dass Benutzer leicht zwischen diesen Ebenen wechseln können, nicht zuletzt, weil Benutzer möglicherweise nicht wissen, welchen Grad an Allgemeinheit sie tatsächlich benötigen, wenn sie anfangen. Als einfaches Beispiel wies @Jutho darauf hin, dass Benutzer im Beispiel der Bildverarbeitung von @jdbates möglicherweise verschiedene Teilmengen von Indizes unterschiedlichen Geometrien AbstractTensorArray s in einzelne kartesische Standardvektorräume und "Sequenz" -Räume - dann wird es fast nahtlos. Benutzer einer niedrigeren Funktionsebene müssen die höheren Ebenen erst kennen oder sich darum kümmern, wenn sie sie benötigen.

Jeder Teil davon, für den es klar und vorhersehbar ist - für die relevanten Benutzer -, wie die Hierarchie aussehen sollte, könnte vernünftigerweise in Base gehen. Die eigentliche Frage sollte lauten: ob für Array-Operationen, lineare Algebra oder Tensoralgebra - wie wahrscheinlich ist es, dass ein Benutzer dieser Art von Funktionalität dies auf andere Weise erwartet oder wünscht?

Viele Leute mögen die lächerliche Granularität der Anzahl der Importe nicht, die Sie benötigen, um etwas in Python-Land zu erledigen. Ich glaube nicht, dass irgendjemand vorschlägt, so weit zu gehen.

Es gibt jedoch ein wichtiges Argument dafür, dass eine große Anzahl von Abhängigkeiten und das Aufblähen von Bibliotheken für einige Anwendungsfälle unerwünscht sind. Julia, die Sprache sollte eigentlich nicht erfordern, dass Fortran-Laufzeitbibliotheken auf Ihrem Computer installiert sind, aber im Moment tut dies die Julia-Standardbibliothek, unabhängig davon, ob dies der Fall ist oder nicht Der Code, den Sie ausführen möchten, führt eine beliebige lineare Algebra aus (oder Bessel-Funktionen oder FFTs usw.). Dies alles wird jedoch durch # 5155 und andere Probleme gut abgedeckt - "Standardmäßig installiert" und "Für jede Funktionalität mindestens erforderlich" sollten eventuell in verschiedene Modulgruppen aufgeteilt werden.

Danke Tony, ich stimme dem zu. Darüber hinaus ist es mit unserem Paketsystem wirklich kein Problem, "Toolbox" -ähnliche Pakete zu haben, die mehrere zusammenfassen. Mit dem Makro @reexport funktioniert dies gut.

Aber es gibt noch einen anderen Punkt. Innerhalb eines Pakets ist es viel einfacher, eine Idee voranzutreiben. Wenn man etwas ändern will, macht man es einfach. Innerhalb der Basis ist man viel eingeschränkter.

Es scheint, dass ein Patchwork von Paketen zwar eine größere Unabhängigkeit ermöglicht, aber auch eine fragmentierte Plattform schafft, die schwieriger zu navigieren ist. Es scheint, dass die Verwendung einer einheitlicheren, verallgemeinerten Struktur die interdisziplinäre Interaktion und Erforschung neuer Bereiche rationalisieren und erleichtern würde.

Was ist derzeit die idiomatischste Art, eine Vektor * -Matrix zu erstellen?
Angenommen, ich habe X mit Form (K, N) und b mit Form (K,) , und ich möchte mit b auf der multiplizieren links, um einen Vektor der Länge (N,) .
Rufe ich BLAS.gemv('T',1.0,X,b) ?
Oder reshape(b'*X,size(x,2))
Beide sind etwas hässlich.

Ich denke du könntest X'b

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

Was ist derzeit die idiomatischste Art, eine Vektor * -Matrix zu erstellen?
Angenommen, ich habe X mit Form (K, N) und b mit Form (K,) und ich möchte
links mit b multiplizieren, um einen Vektor der Länge (N,) zu erhalten.
Rufe ich BLAS.gemv auf ('T', 1.0, X, b)
Oder umformen (b '* X, Größe (x, 2))
Beide sind etwas hässlich.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/JuliaLang/julia/issues/4774#issuecomment -79405868.

Ja, ich hatte gedacht, das würde eine Kopie von X auslösen.
Aber @time scheint darauf hinzudeuten, dass es keine Kopie gibt, basierend auf der Speicherzuweisung

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)

Wir machen Lust auf das Parsen von 'so X'y endet als Ac_mul_B(X,y) und macht das
der gleiche BLAS-Anruf, den Sie vorgeschlagen haben.

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

Ja, obwohl ich gedacht hatte, dass dies eine Kopie von X auslösen würde.
(Aber @time https://github.com/time scheint darauf hinzudeuten, dass es keine gibt
Kopie, basierend auf der Speicherzuordnung

Julia> X = Rand (1000, 1000); y = Rand (1000);

julia> @time y'X;
verstrichene Zeit: 0,00177384 Sekunden (15 kB zugewiesen)

julia> @time X'y;
verstrichene Zeit: 0,000528808 Sekunden (7 kB zugewiesen)

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/JuliaLang/julia/issues/4774#issuecomment -79421713.

Ob Sie es glauben oder nicht, eine Zusammenfassung dieses gesamten Threads nähert sich dem Materialisieren.

Ein letzter Punkt: Hat jemand darüber nachgedacht? ' könnte eigentlich ein ganz anderes Tier sein als '? Letzterer hat die richtige Struktur, um ein Dual zu sein, aber. ' funktioniert nicht, wenn das zugrunde liegende Feld nicht die reellen Zahlen sind. Ist es verrückt zuzuweisen. ' der Begriff "alle Indizes umkehren", alle Begriffe der Dualität zu transponieren und zu reservieren, der ein "lustiges Zeilenvektor" / hermitisches Konjugat erzeugt?

Ich denke , dass in jedem Fall conj(transpose(x)) äquivalent sein sollte ctranspose(x) .

Wie oben dargelegt, hoffe ich, dass keiner der Wrapper-Typen, die transpose und ctranspose erstellen, einen Namen erhält, der ein bestimmtes mathematisches Konzept wie dual . Eine wissenschaftliche Sprache wie Julia sollte eine Reihe nützlicher Datenstrukturen bereitstellen und gemeinsame Operationen implementieren, aber ich denke, es wäre ein Fehler, eine strenge mathematische Struktur zuzuordnen, da dies für einige Anwendungen nicht geeignet und für zu komplex ist Andere. Es ist Sache des Benutzers, diese Datenstrukturen und Operationen zu verwenden, um die mathematischen Operationen in seiner Anwendung zu implementieren. Es gibt sicherlich Anwendungen für z.' * A * z die einen Skalar zurückgeben, wobei z ein komplexer Vektor und A eine Matrix ist, obwohl dies nichts mit einem inneren Produkt und / oder Dual zu tun hat Vektoren.

@jutho Könnten Sie einen Anwendungsfall für z.' * A * z angeben ?

Wenn z1 und z2 die Darstellung zweier komplexer Vektoren Z1 und Z2 (z. B. Tangentenvektoren im holomorphen Teil des Tangentenraums einer Kähler-Mannigfaltigkeit ) und a ist die Matrixdarstellung eines Tensors A mit zwei kovarianten Indizes (z. B. einer komplexen (2,0) -Form der Kähler-Mannigfaltigkeit), dann A(Z1,Z2) = z.' * a * z .

Beachten Sie, dass ich hier betone, dass die Julia-Objekte z1 , z2 und a nur eine _darstellung _ bestimmter mathematischer Objekte (in Bezug auf eine ausgewählte Basis / Koordination) bilden und daher Operationen an Datenstrukturen können nicht eindeutig mathematischen Operationen zugeordnet werden, ohne zu wissen, was diese Datenstrukturen darstellen.

@ Jutho danke. Ihr Standpunkt zu Darstellungen ist gut aufgenommen und wurde in dieser Diskussion mehrfach vertreten. Ich versuche, die minimale Schnittstelle von Vektoren und Matrizen zu finden und herauszufinden, ob diese minimale Schnittstelle überhaupt nicht mit der minimalen Schnittstelle von Arrays kompatibel ist und was wir in benutzerdefinierte abstrakte Datentypen auslagern können.

An dieser Stelle bin ich voll und ganz für den Vorschlag von @StefanKarpinski , der auch hauptsächlich von @andyferris oben bestätigt wird. Speziell

  1. Indizierung im APL-Stil
  2. v 'gibt eine Art Covector- oder Transponiertyp an

Alles andere ist ein Detail. Es könnte schön sein, row(M,i) und col(M,i) Funktionen hinzuzufügen. Andernfalls benötigen Sie M[i,:].' , um eine Zeile als Covector zu extrahieren. IIUC, M[i,:]' würde ein conj , das in diesem Fall nicht gewünscht wird?

Ja, ich sollte hinzufügen, dass ich die nette Indizierung im APL-Stil beim letzten Schreiben nicht voll und ganz geschätzt habe, aber jetzt bin ich voll und ganz dafür, diese Änderung vorzunehmen. Dies wiederum macht das Dual-Vektor-Zeug noch überzeugender und die row / col -Funktionen.

Seitdem habe ich versucht, mit einer Implementierung herumzuspielen, und das verwirrendste war, ob der aus einer Matrix extrahierte Covektor konjugiert war oder nicht…

Ich denke, Sie möchten die Literalwerte der Matrix übernehmen. Erweitern Sie mit der Dirac-Notation die (beliebige) Matrix M = sum_i | i>zB [0,0,1, ..., 0] wollen wir extrahierenU wir erhalten row(U,i)' = col(U’,i) , was vollkommen sinnvoll ist, um linke und rechte Eigenvektoren aus einer Eigendekomposition zu extrahieren.

Andy

Am 15. März 2015, um 21.36 Uhr, schrieb Jeff Bezanson [email protected] :

An dieser Stelle bin ich voll und ganz für den Vorschlag von @StefanKarpinski https://github.com/StefanKarpinski , der auch hauptsächlich von @andyferris https://github.com/andyferris oben bestätigt wird. Speziell

Indizierung im APL-Stil
v 'gibt eine Art Covector- oder Transponiertyp an
Alles andere ist ein Detail. Es könnte hilfreich sein, Zeilen- (M, i) und Spaltenfunktionen (M, i) hinzuzufügen. Andernfalls benötigen Sie M [i,:], um eine Zeile als Covector zu extrahieren. ' ? IIUC, M [i,:] 'würde ein Conj in diesem Fall nicht gewollt?

- -
Antworten Sie direkt auf diese E-Mail oder sehen Sie sie auf GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -81228816 an.

Gibt es Freiwillige, die daran arbeiten? zB @mbauman , @jakebolewski , den ich überrascht sehe, sind noch nicht in diesem Thread :)

Es mag mühsam sein, alles zu finden, was geändert werden muss, aber die grundlegende Änderung des Indizierungsverhaltens sollte nicht schlecht sein. Wahrscheinlich können @jiahao und @andreasnoack mehr darüber sagen, wie Covectors eingebaut werden sollten, z. B. wie ihr Supertyp sein sollte, wenn überhaupt.

Wir brauchen 9, keine 8 weiteren Kommentare, bevor wir damit fortfahren können.

Ich kann dabei helfen.

wir sind ziemlich nah

Als relevanter Kommentar sollte ein Wrapper-Typ Transpose und CTranspose auch ein einfacher Wrapper-Typ Conjugate , so dass conj(A) ist auch faul. Für das Multiplizieren von Matrizen mit BLAS ist dies nicht wirklich nützlich, da es keine spezielle Unterstützung dafür gibt (und C in BLAS bedeutet hermitisches Konjugat), aber wenn es jemals eine vollständige Julia BLAS-Implementierung gibt, wäre es großartig, auch zu unterstützen conj(A)*B ohne explizite Konjugation.

Zum einen habe ich das Gefühl, dass ich Vektortransponierungen jetzt viel ernster nehme als vorher.

Vielleicht können uns @andreasnoack und @simonbyrne sagen, ob # 6837 noch einmal besucht werden sollte.

Ich stimme @simonbyrne zu, dass wir nicht Transpose{Array} <: AbstractArray .

Andere allgemeine Gedanken:

  • Ich habe festgestellt, dass die Berechnung des äußeren Produkts derzeit eine Ausnahme von der Regel "Nachfolgende Singleton-Dimensionen nicht automatisch hinzufügen" enthält. Wenn u die Größe (n,) , ist u * u' eine Berechnung mit (n,) x (1, n) . Dieses Produkt kann nicht mit den üblichen Regeln der Matrixmultiplikation berechnet werden, es sei denn, wir formen das erste Argument automatisch in (n, 1) .
  • Die Regel "Nachfolgende Singleton-Dimensionen automatisch hinzufügen" der MATLAB-Indizierungssemantik ist grundsätzlich nicht mit der Regel "Transponieren kehrt alle Dimensionen um" kompatibel. Mit der ersteren Regel sind Arrays der Form (n,) semantisch identisch mit umgeformten Arrays der Form (n,1) und der Form (n,1,1) usw. Beachten Sie jedoch, dass, wenn die Transponierung alle Dimensionen umkehrt, die Die resultierenden Arrays haben die Form (n,) , (1, n) und (1,1,n) . Diese können nicht gleichwertig sein, wenn Sie nur nachfolgende Singletons hinzufügen dürfen. Wenn man dies auf das logische Extrem bringt, kann die Transponierung eines Arrays eine beliebige Anzahl von führenden Singletons haben und hat daher eine mehrdeutige Form, die logisch inkonsistent ist.

Ich habe auch eine Literaturumfrage durchgeführt und einige interessante APL-Geschichten entdeckt. Was wir als APL-Indexierungsregel bezeichnet haben, war nicht in Iversons Buch von 1962 enthalten, sondern existierte in APL \ 360 (1968; die erste Implementierung von APL). APL \ 360 verschmolz jedoch Skalare und 1-Vektoren, eine Inkonsistenz, die in der Literatur erst erkannt wurde (Haegi, 1976). Eine Diskussion über die formale Semantik mehrdimensionaler Arrays erschien zuerst in Browns Doktorarbeit (1972; er entwarf später APL2) und spornte eine Reihe von Arbeiten an, die ihre Semantik formalisierten.

Eine hervorragende Übersicht über die Entwicklungen, die zu APL2 führen, ist:

  • Karl Fritz Ruehr. "Eine Umfrage zu Erweiterungen von APL." In Proceedings of the International Conference on APL, APL '82, S. 277–314, New York, NY, USA, 1982. ACM.

Bemerkenswert in der Literatur für die Beachtung der Indexierungsregeln sind:

  • T. Mehr. "Axiome und Theoreme für eine Theorie der Arrays." IBM Journal of Research and Development, 17 (März): 135–175, 1973.

    • Ein riesiger Band, der die Semantik mehrdimensionaler Arrays mithilfe der axiomatischen Mengenlehre von Quine formalisiert und zeigt, wie Skalare mit Rang-0-Arrays mit dem Begriff der in sich geschlossenen Mengen kombiniert werden können, um das zu konstruieren, was die APL-Literatur "schwebende Arrays" nennt. ([1] == 1, im Gegensatz zu "Grounded Arrays", wobei [1]! = 1. Spätere Arbeiten von Sheila M. Singleton in ihrer Masterarbeit von 1980 zeigten, dass die Array-Theorie von More auch zur Beschreibung von Grounded Arrays angepasst werden kann.)

    • Mehr erwähnt auch das innere Produkt, das einen Skalar zurückgibt, als wichtige Regel für die Steuerung der Array-Semantik.

    • Mehr spielt auch auf die Komplexität der Handhabung von "Auf / Ab" für allgemeine mehrdimensionale Arrays an:

      "Sei V ein n-dimensionaler Vektorraum über einem Feld. Ohne Berücksichtigung von Kontravarianz und Kovarianz ist ein Tensor der Valenz q auf V eine multilineare Abbildung des kartesischen Produkts der Liste VV ... V der Länge q in einen Vektor Wenn V eine Basis hat, dann kann ein Vensortensor q auf V durch einen Komponententensor dargestellt werden, der ein Array auf q Achsen mit jeweils der Länge n ist. "

  • G. Lewis. "Ein neues Array-Indexierungssystem für APL", Tagungsband der siebten internationalen Konferenz über APL - APL '75, 234-239, 1975.

    • Dieses Papier war das erste, das befürwortete, dass das Indextupel in einer Indexierungsoperation ein erstklassiges Objekt in APL ist, und stellte fest, dass durch systematische kartesische Produkte entlang jedes Ranges des Indextupels eine konsistente Konstruktion des Indexierungsergebnisses erreicht werden kann.

  • Hans R Haegi. "Die Erweiterung von APL auf baumartige Datenstrukturen." ACM SIGAPL APL Quote Quad, 7 (2): 8–18, 1976.

    • Dieses Papier beklagte sich über eine Inkonsistenz in der klassischen APL \ 360, bei der Skalare und 1-Arrays zusammengeführt wurden, und befürwortete, dass die APL-Indexierungsregel diese Zusammenführung nicht gelten ließ.

    • Dieses Papier enthält auch eine Konstruktion, die Lewis, 1975, sehr ähnlich ist; Die Arbeit scheint unabhängig zu sein.

  • JA Gerth und DL Orth. "Indizieren und Zusammenführen in APL." In Proceedings of the International Conference on APL, APL '88, Seiten 156–161, New York, NY, USA, 1988. ACM.

    • Es wird darauf hingewiesen, dass die APL-Indizierungsregel gerechtfertigt werden kann, indem die Indizierung als Rundfunkoperation betrachtet wird, bei der der Indexsatz dem Wertesatz zugeordnet wird. Diese funktionale Interpretation legt natürlich die Rangerhaltungsregel und die kartesische Konstruktion von Lewis und Haegi nahe.

Ohne die Regel "kann nachfolgende Singleton-Dimensionen hinzufügen" gehorchen wir der Identität nicht

image

da die linke Seite ein Skalar ist (per Definition der Spur) und die rechte Seite die Form (1, n) x (n, n) x (n,) = (1,) . Umgekehrt können wir diese Identität als Leitprinzip für die Auswahl der Vektortranspositionssemantik verwenden. Der Hauptpunkt ist die zyklische Eigenschaft der Ablaufverfolgungsoperation, die die erste Identität definiert. Die Menge innerhalb der Kurve muss entweder ein Skalar oder eine Matrix sein (die Kurve eines Vektors ist nicht definiert). Avv' ist bereits eindeutig eine Matrix: (Av)v' und A(vv') führen zum gleichen Ergebnis. Aber auch ab der zweiten Größe muss v'Av eine Matrix oder ein Skalar sein. (Wenn skalar, gilt auch die zweite Identität.) v'Av kann nur dann ein 1-Vektor sein, wenn die Regel "Nachfolgende Singleton-Dimensionen hinzufügen" aktiv ist. In diesem Fall kann sie transparent in eine 1x1-Matrix umgeformt werden.

Wenn wir also möchten, dass die Ablaufverfolgung definiert wird, werden die zulässigen Formen der äußeren Produkte vv' und der quadratischen Formen v'Av zwangsläufig eingeschränkt.

@jihao : Ich bin nicht ganz sicher, was Sie für unsere dagegen argumentieren. Können Sie die Regel "Kann nachfolgende Singleton-Dimensionen hinzufügen" etwas deutlicher formulieren? Wann gilt es? Denken Sie, dass wir es unterstützen sollten?

Ich denke, dass einige Ihrer Argumente dazu dienen können, die Position zu stärken, dass wir die Transposition nicht als Umkehrung aller Dimensionen betrachten können (ein Spaltenvektor würde in einen Spaltenvektor oder möglicherweise ein Array mit einer beliebigen Anzahl führender Singleton-Dimensionen transponiert). Um mit der Matrixalgebra konsistent zu bleiben, muss sie meiner Meinung nach stattdessen als Austausch der beiden ersten Dimensionen angesehen werden. Dann glaube ich, dass einige der Widersprüche, die Sie angeben, verschwinden. Hoffentlich verschwindet der Rest, wenn wir beim Transponieren keine Singleton-Dimensionen hinzufügen (die fehlende erste Dimension des Covectors zählt nicht).

Meine allgemeinen Kommentare oben sind nicht, diese Regeln zu befürworten, sondern um den Entwurfsraum von Array-Indexierungsregeln und ihre Interaktion mit linearen Algebra-Identitäten abzugrenzen.

Die Regel "kann nachfolgende Singleton-Dimensionen hinzufügen" wird von MATLAB verwendet und (laut @alanedelman) eingeführt, um mehrdimensionale Arrays zu unterstützen. Die in MATLAB-Arrays verwendete Index-zu-Offset-Berechnung wird durch sub2ind , die denselben linearen Index zurückgibt, unabhängig davon, wie viele nachfolgende Einsen Sie darauf werfen. Darüber hinaus heißt es in der Matrixindizierungsdokumentation von MATLAB, dass für Indizierungsvorgänge:

Die Anzahl der für B angegebenen Indizes, einschließlich nachfolgender Indizes gleich 1, überschreitet nicht ndims (B).

Formaler denke ich, dass es wie folgt angegeben werden kann:

Für Operationen, die Arrays als Eingabe verwenden, befinden sich (n,) -Arrays, (n,1) -Arrays, (n,1,1...) Arrays alle in derselben Äquivalenzklasse, um gültige Argumente für diese Operationen zu sein. (Wenn n=1 , enthält die Äquivalenzklasse auch Skalare.)

Beispiele:

  • A*b und A\b wobei A eine Matrix ist und b ein Vektor oder eine Matrix sein kann (identische Semantik für n x 1 Matrizen),
  • hcat(A, b) wobei A eine Matrix ist und b ein Vektor oder eine Matrix sein kann

Zum größten Teil verfügt Julia nicht über diese Regel "Kann nachfolgende Singleton-Dimensionsregeln hinzufügen", mit Ausnahme des äußeren Produktbeispiels. Möglicherweise gibt es auch andere, aber ich kann momentan nicht an sie denken.

Solange Transpose{A<:AbstractArray} kein Subtyp von AbstractArray glaube ich, sehe ich kein Problem (aber wahrscheinlich übersehen ich etwas, da ich nicht so viel darüber nachgedacht habe wie ihr). ::
mit

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

(und ähnlich für CTranspose ) können wir haben

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

Die einzige offene Frage ist, ob AbstractVector * AbstractTMatrix soll, wenn AbstractTMatrix 1 als erste Größe hat, oder ob AbstractVector * AbstractVectorTranspose ausreicht.

Das neue Typsystem kann auch dazu beitragen, einige dieser typealias und union etwas sorgfältiger auszudrücken.

Wenn beispielsweise v.'*A als (A.'*v).' berechnet wird, wird die Notwendigkeit eines Conjugate Wrappers angezeigt, wenn A selbst zB A=B' .

Ich stimme @simonbyrne zu, dass wir nicht Transpose{Array} <: AbstractArray .

Können Sie dort näher darauf eingehen? Ich dachte, die Meinung in https://github.com/JuliaLang/julia/issues/4774#issuecomment -59428215 war, dass CoVector kein Subtyp von AbstractVector sein sollte, aber es scheint mir ein wenig seltsam, nicht Transpose{Matrix} <: AbstractArray .

Für das, was es wert ist, denke ich, dass sich CoVector meistens wie Vector verhalten sollte, außer dass Vector Matrix als Spaltenmatrix in CoVector konvertiert als Zeilenmatrix in Matrix .

Ich denke, das würde bedeuten, dass die Indizierung in einen Covector genauso funktionieren sollte wie in eine Zeilenmatrix.

Das ist ein interessanter Gedanke. Werden die Dinge einfacher oder komplizierter, wenn bei Transponierungs- / Covektortypen nur führende Singleton-Dimensionen entfernt werden?

(Ich habe dieses Problem mit Interesse verfolgt, aber meine lineare Algebra ist rostig genug, dass ich mich nicht qualifiziert fühlte, einen Beitrag zu leisten.)

@ Bauman :

Werden die Dinge einfacher oder komplizierter, wenn nur führende Singleton-Dimensionen in Transponierungs- / Covektortypen entfernt werden?

Wenn Sie eine beliebige Anzahl führender Singleton-Dimensionen zulassen, sind die Array-Indizes nicht mehr gut geordnet, und es gibt keine "erste" Dimension, die bizarr genug ist, um die Ordnung auch als Axiom zu betrachten.

@tkelman :

Ich dachte, die Meinung in # 4774 (Kommentar) war, dass CoVector kein Subtyp von AbstractVector sein sollte, aber es scheint mir ein wenig seltsam, Transpose {Matrix} <: AbstractArray nicht zu haben.

Mir ist klar geworden, dass es in diesem Problem ausschließlich darum geht, die Array-Semantik (vgl. # 10064) von der linearen Algebra-Semantik zu trennen und nach Orten zu suchen, an denen sie vermischt sind.

  • Array-Semantik wird durch Funktionen wie Größe, Länge, getindex, setindex, hcat, vcat, reshape, rot90 ... definiert.
  • Die Semantik der linearen Algebra wird durch Funktionen wie +, -, *, / ,, ', trace ... definiert.

Wenn wir die cat -Funktionen als Teil der wesentlichen Schnittstelle eines AbstractArray , ist Transpose{<:AbstractArray} eindeutig kein AbstractArray da sich ihr Verkettungsverhalten unterscheidet . Wenn wir nur Form und Indizierung als Teil der wesentlichen Schnittstelle betrachten, ist die Situation weniger klar.

Wenn wir eine Verkettung als Teil der wesentlichen Schnittstelle eines AbstractArray benötigen, ist es auch einfacher zu rechtfertigen, warum Typen wie SymTridiagonal keine AbstractArray , da Verkettungsoperationen über SymTridiagonal s wie [SymTridiagonal(randn(5), randn(4)) randn(5)] sind derzeit nicht definiert.

@toivoh :

Ich denke, das würde bedeuten, dass die Indizierung in einen Covector genauso funktionieren sollte wie in eine Zeilenmatrix.

Es gibt Identitäten, die darauf hindeuten, dass das Indizierungsverhalten von Transpose{Vector} das gleiche sein sollte wie bei normalen Vector . Bedenken Sie, dass für numerische Typen v[1] das gleiche Ergebnis wie v' * e₁ = v ⋅ e₁ und v[1:2] das gleiche Ergebnis wie v' * [e₁ e₂] , wobei e₁ ist Die kanonische Basis Vector{Int} [1, 0, 0, ...] und e₂ ist [0, 1, 0, 0, ...] . Wenn wir diese Identitäten in Bezug auf Indexierung, innere Produkte und Umsetzung benötigen, könnte man das behaupten

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

(wobei der erste Schritt ein neues Axiom ist und der vierte die Tatsache ausnutzt, dass das Transponieren eines Skalars ein No-Op ist), so dass das Indizieren in Transpose{Vector} die Transposition im Wesentlichen ignorieren und das Indizieren in CTranspose{Vector} würde die indizierten Elemente konjugieren.

In dieser Ausgabe geht es ausschließlich darum, die Array-Semantik (vgl. # 10064) von der linearen Algebra-Semantik zu trennen und nach Orten zu suchen, an denen sie vermischt sind.

  • Array-Semantik wird durch Funktionen wie Größe, Länge, getindex, setindex, hcat, vcat, reshape, rot90 ... definiert.
  • Die Semantik der linearen Algebra wird durch Funktionen wie +, -, *, / ,, ', trace ... definiert.

+1 zu diesem Gesichtspunkt und nicht Transpose <: AbstractArray . Wenn Sie einen Covektor indizieren, sollte dies auch mit einem einzelnen Index erfolgen, da andernfalls das Ergebnis des Covector * -Vektors (Kontraktion über einen einzelnen Index) nicht zu einem Skalar (einem Objekt mit Nullindizes) führen könnte.

@jihao : Ich bin nicht sicher, warum wir brauchen oder wollen

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

als neues Axiom. Selbst wenn ein Covector als Zeilenmatrix indizieren würde, würden wir aufgrund der linearen Indizierung das gleiche Ergebnis für die oben genannten erhalten.

Und +1, um Covektoren als mit linearer Algebra befasst zu sehen.

Aber es gibt keinen Grund, warum die Verkettung mit einem SymTridiagonal nicht definiert werden sollte, oder?

Die lineare Indizierung von umgeformt und dann der neue Vektor indiziert wird. Damit die lineare Indizierung für transponierte Arrays das gleiche Ergebnis liefert, müssen zuerst die Indexierungsregeln für transponierte Vektoren definiert werden, damit wir daraus die linearen Indexierungsregeln ableiten können. (Außerdem pingen Sie jemand anderen an.)

Ich dachte, bei der linearen Indizierung geht es um die Speicherreihenfolge, und es gibt wirklich keine andere sinnvolle Reihenfolge für die lineare Indizierung eines Vektors, oder? (Und entschuldigen Sie die Rechtschreibfehler.)

Es gibt keine eindeutige sinnvolle Durchquerungsreihenfolge im Speicher. Selbst für Fortran-Arrays können Sie die Elemente in Spalten-, Zeilen- oder sogar umgekehrter Spalten-Hauptreihenfolge speichern (genau das hat der ursprüngliche IBM Fortran I-Compiler getan). Darüber hinaus gibt es andere Datenstrukturen (siehe # 10064) wie Versuche, die als Arrays verwendet werden können und noch mehr Optionen für die Durchlaufreihenfolge bieten.

Gleiches gilt für Vektoren und Matrizen. Da die lineare Indizierung für eine Spaltenmatrix und ihre Transponierung (und dieselbe wie für einen Spaltenvektor) in derselben Reihenfolge auf die Elemente zugreift, warum sollte ein Covektor anders sein? Wenn es anders sein sollte, sollte es meiner Meinung nach sein, dass wir die Indizierung für Covektoren überhaupt nicht definieren würden.

@toivoh ja die gleichen Definitionen gelten für gewöhnliche Arrays. Dies ändert nichts an der Tatsache, dass die lineare Indizierung in beiden Fällen von der normalen (Tupel?) Indizierung abgeleitet ist.

Das Indizieren von Transpose -Objekten ist möglicherweise nicht zulässig. Ich sage nicht, dass sie indizierbar sein müssen, aber wenn ja, müssen sie nicht unbedingt das gleiche Indizierungsverhalten haben. Um konservativ zu sein, können wir die Indizierung vorerst undefiniert lassen und prüfen, ob Anwendungsfälle angezeigt werden.

Viele reine Julia-Implementierungen linearer Algebra-Funktionen möchten in eine Transponierte indizieren, nicht wahr? Das Schreiben einer reinen Julia-Matrixmultiplikation (für Nicht-BLAS-Zahlentypen) wird einfach, wenn Sie für die beiden beteiligten Matrizen nicht zwischen einem möglichen Fall (normal, transponieren, ctransponieren, konj?) Unterscheiden müssen, sondern sie nur als gewöhnliche Matrizen behandeln müssen. Cache-ahnungslose Methoden könnten versucht werden, ein etwas lokales Speicherzugriffsmuster zu erhalten.

Richtig, duh.

@ Jutho : Ich stimme zu. Und um dort hinein zu passen, sollten Covektoren wie Zeilenmatrizen indizieren, oder?

@toivoh , wenn Sie meinen, dass sie einen zusätzlichen Index 1 vor sich haben sollten, bin ich anderer Meinung und sehe nicht, wie dies durch meine Aussage impliziert wird. Ich habe nur über Matrixmatrixprodukte gesprochen. Matrix * Vektor oder Covector * Matrix sind verschiedene Methoden, die unterschiedliche Funktionsdefinitionen erfordern, nicht nur, weil sie ein anderes Speicherzugriffsmuster haben, sondern auch, weil sie einen anderen Rückgabetyp haben (Matrix_vector = Vektor oder Covector_matrix = Covector) sehr praktischer Grund in Julia, diese Dinge nicht zu mischen.

Im Allgemeinen bin ich kein großer Fan der Fähigkeit, beim Indizieren eines N-dimensionalen Arrays zusätzliche Indizes 1 hinzuzufügen, oder beispielsweise des Alias ​​vom Typ VecOrMat . Dies ermöglicht eine schlampige Programmierung, erleichtert aber auch, Fehler zu machen oder andere Fehler langsamer zu erkennen. Ich sehe nur zwei nützliche Möglichkeiten, um ein N-dimensionales Array zu lösen, nämlich mit exakten N-Indizes, falls Sie es als multilineares Objekt verwenden, oder mit einem linearen Index, wenn Sie es als Vektor in einem Tensorprodukt behandeln Leerzeichen (z. B. zum Hinzufügen von zwei solchen Arrays oder zum Multiplizieren mit einem Skalar). Während das für meine Verwendung ausreichend ist, kann ich akzeptieren, dass dies für andere zu begrenzt ist.

@Jutho : Ok, ich stimme zu, dass es wahrscheinlich keine Rolle spielt, da der Rückgabetyp sowieso anders sein muss.

Hier ist ein Versuch zu beschreiben, was wir versuchen, und einige Axiome zu geben:

Ich denke, wir sind auf einen ziemlich klaren Konsens gestoßen, dass der Ausgangspunkt die Matrixalgebra ist (die ausschließlich auf Matrizen basiert). Für die meisten Operationen wissen wir, wie sie sich in der reinen Matrixeinstellung verhalten.

Ich glaube, wir versuchen, die reine Matrixeinstellung auf konsistente Weise zu erweitern und zu verfeinern, um auch echte Skalare und Vektoren zu erhalten, und weil es für die Konsistenz notwendig zu sein scheint, Covektoren.

Hier ist meine Ansicht von Skalaren und Vektoren (vom Standpunkt der reinen Matrix aus gesehen): Ein Skalar ist eine Matrix, die aufgrund ihres Typs auf 1 x 1 beschränkt ist. Ein Vektor ist eine Matrix, die aufgrund ihres Typs auf 1 x 1 beschränkt ist sei nx 1. (Ich werde unten argumentieren, dass ein Covektor eine Matrix ist, die aufgrund ihres Typs auf 1 x n beschränkt ist.) Unter diesem Gesichtspunkt können wir zwei Axiome angeben: (im Folgenden nicht sehr formal beschrieben)

  • Erweiterung: Betrachten Sie eine Funktion von Matrizen zu Matrizen. Wenn immer eine Ausgabematrix mit einer Größe von 1 in einer bestimmten Dimension bei bestimmten Eingaben bestimmter Typen erzeugt wird, wird diese Tatsache im Typ der Ausgabe codiert (was sie zu einem Skalar / Vektor / Covektor macht).
  • Verfeinerung: Wenn eine Funktion, die ein Matrixargument in der reinen Matrixeinstellung annehmen würde, erfordert, dass die Eingabe eine Größe von eins in einer oder mehreren Dimensionen hat, kann sie die Annahme einer Eingabe verweigern, wenn diese Tatsache nicht im Typ codiert ist.

Wenn wir dem oben Gesagten zustimmen, muss die Transponierung eines Vektors die oben beschriebene Art von Covektor sein: Die Transposition der anx 1-Matrix ergibt eine 1 xn-Matrix. Wenn wir die Tatsache codieren, dass die Größe entlang der ersten Dimensionen des Ergebnisses immer eins ist, erhalten wir den Covector wie oben beschrieben.

Wenn Sie aus der Sicht der Matrixalgebra beginnen, ist das, was Sie sagen, richtig. Dies ist das Modell, das MATlab wahrscheinlich ziemlich perfekt implementiert. Alles ist eine Matrix. Es ist ein geschlossenes System, alle Operationen an Matrizen erzeugen neue Matrizen.

Ich hatte sicherlich den Eindruck, dass der Sinn dieses Problems (Vektortransponierungen ernst zu nehmen, da es sich nicht nur um Matrizen handelt) darin bestand, von diesem Matrixalgebra-Modell wegzukommen, da es Inkonsistenzen zeigt, wenn Sie Zahlen aus Gründen von 1x1-Matrizen trennen möchten der Effizienz. Die Alternative besteht dann darin, dem Modell der linearen Algebra zu folgen, bei dem klar zwischen dem Feld (Skalare), dem Vektorraum (und dem entsprechenden Dual) und dem Raum der linearen Operatoren / Transformationen (Matrizen) unterschieden wird.

Ich denke, dass lineare Algebraoperationen in Julia ziemlich stark in der Matlab-Tradition verwurzelt sind, mit einigen bemerkenswerten Ausnahmen wie Skalaren und Vektoren und dem Versuch, den Benutzer nicht zu überdenken. Alles, was sich zu weit davon entfernt, ist wahrscheinlich eine sehr große Störung.

Ich glaube, dass meine obigen Axiome dazu beitragen sollten, die Inkonsistenzen zu beheben, die auftreten, wenn Sie Skalare und Vektoren von Matrizen trennen möchten (aus Gründen der Effizienz und Korrektheit). Aber ich bin definitiv offen für andere mögliche Systeme.

Ich stimme @Jutho hier zu; In dieser Ausgabe geht es darum, sich von MATLABs Semantik "Alles ist eine Matrix" zu entfernen. Die MATLAB-Regel "kann nachfolgende Singleton-Dimensionen hinzufügen" ist erforderlich, damit das Universum unter linearen Algebraoperationen geschlossen wird. Diese Regel definiert jedoch Äquivalenzklassen, die Mitglieder vom Typ T und andere vom Typ Array{T,N} all N und ist der Hauptgrund, warum das Typsystem von MATLAB unentscheidbar ist. (Siehe Satz 1 von Joisha und Banerjee, 2006 - obwohl das Ergebnis in Form angegeben wird, besteht das Problem tatsächlich darin, wie eine Änderung des Array-Ranges die Programmsemantik ändern kann.)

Aber ich denke immer noch, wir hatten einen ziemlich guten Konsens darüber, dass die Multiplikation von Skalaren, Vektoren, Covektoren und Matrizen assoziativ sein sollte (mit Ausnahme von Dingen wie (v'*v)*v denen zwei Nicht-Skalare multiplizieren, um einen Skalar zu erzeugen), und das zB v'*M*v sollte einen Skalar erzeugen, M*v einen Vektor und v'*M einen Covektor. Ich bin mir nicht sicher, wie weit es möglich ist, von der Matrix-Semantik abzuweichen, während diese Bedingungen noch erfüllt sind.
Was wollen wir noch vermeiden und welche Eigenschaften möchten wir stattdessen gewinnen?

Wie viel schlimmer ist es, wenn wir in einigen Fällen nur T und Array{T,0} ? (ZB Indizierung: M[:,2] erzeugt ein Array{T,1} , aber M[2,2] erzeugt kein Array{T,0} )

Wir können weiterhin eine gültige Semantik mit Transpose{Vector} beibehalten.

Ich habe alle Kommentare zur Assoziativität noch einmal gelesen und bin zu dem Schluss gekommen, dass der größte Teil dieser Diskussion nicht die Assoziativität an sich betrifft, sondern die Semantik innerer und äußerer Produkte. Wenn Sie einen Ausdruck finden, um den es auch nicht geht, weisen Sie ihn bitte darauf hin.

Das Problem mit der Matlab-Semantik ist, dass M*v' und v*M manchmal funktionieren, obwohl dies nicht der Fall sein sollte. Wenn M m x 1 dann ist M*v' gültig und gibt eine äußere produktähnliche Menge zurück (da v' 1 x n ). In ähnlicher Weise kann v*M als das Produkt von n x 1 und 1 x m bewertet werden, wenn M 1 x m und wir die Regel "Nachfolgende Singletons hinzufügen können" haben. 1 x m Matrizen, die wiederum eine äußere produktähnliche Menge ergeben.

Die Frage der Zusammenführung von T und Array{T,0} wurde auch in der APL-Literatur aufgeworfen - in APL werden mehrdimensionale Arrays rekursiv verschachtelt, was die Frage aufwirft, ob Array{T,0} und T sind unterscheidbar. Wenn nicht, handelt es sich um "geerdete Arrays" (die auf T zurückgreifen), andernfalls handelt es sich um "Floating Arrays" (die nur auf Array{T,0} zurückgreifen). Ich glaube, More, 1973 hat tatsächlich bewiesen, dass jede Wahl axiomatisch konsistent ist. Ich bin mir nicht sicher, ob APL jemals die Frage gelöst hat, welche zu verwenden ist, bevor die meisten Praktizierenden in den Ruhestand gingen oder zu etwas anderem übergingen.

@jiahao : Ich wusste nicht, wie grundlegend deine Beobachtung war

v[i] = e_i' * v

lineare Algebra und Indizierungssemantik miteinander zu verbinden. Aber dann müssen Sie auch berücksichtigen

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

Dies zeigt an, dass ein inneres Produkt mit einem Basisvektor von rechts der Indizierung entlang der zweiten Dimension entspricht. Daher würde ich behaupten, dass der i -te Eintrag eines Covectors v' als indiziert werden sollte

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

wo möchten wir natürlich etwas anderes als 1 als ersten Index schreiben, aber was?
Wie auch immer, da wir es erlauben

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

dann sollte 1 auch im obigen Fall als Platzhalter zugelassen werden.

v' * e_i ist ein Skalar, also ist e_1' * (v' * e_i) ein Covektor, kein Skalar. Die Abmessungen stimmen also nicht mit v'[1, i] = e_1' * v' * e_i überein

edit: Ich denke, dies könnte ein Argument gegen das Zulassen von nachgestellten Singletons bei der Indizierung sein?

Ja, die Matrixindizierung ist der nächste logische Schritt, und e_i' * M * e_j ist tatsächlich einer der Ausdrücke, bei denen das Assoziativitätsaxiom nützlich wird, da

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

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

sollte gleich sein. Die Matrixindizierung kann aus einer Vektorindizierungsregel und einer Covektorindizierungsregel abgeleitet werden.

Ich glaube, dass eine konsistente Lösung dieses Problems erfordern kann, dass Ausdrücke wie v[i, 1] nicht zugelassen werden, da die Regel dieses Indizierungsverhalten zulässt
a) sollte dazu führen, dass falsche Fälle für A*v' und v*A funktionieren (ersteres funktioniert, letzteres jedoch nicht, weil wir die nachfolgende Singleton-Regel inkonsistent anwenden), und
b) Wenn wir die Gleichheit von v[i] = v[i, 1, 1, 1] berücksichtigen, würde die entsprechende Covector-Indexierungsregel wie (v')[1, 1, 1, i] aussehen und man muss die entsprechende Regel "Beliebige Anzahl führender Singletons zulassen" für Covectors haben, um die Konsistenz zu gewährleisten. Das Fehlen einer eindeutig definierten ersten Dimension finde ich sehr beunruhigend.

Ich glaube nicht, dass diese Index-Argumentation wirklich irgendwohin führt. Die Indizierung ist eine allgemeine Eigenschaft von N -dimensionalen Arrays, und dass sie als einfache Matrixausdrücke für N=1 oder N=2 ist eher trivial und enthält keine fundamentalen Eigenschaften Information. Dies lässt sich jedoch nicht auf Arrays mit höherem Rang verallgemeinern und sollte daher nicht verwendet werden, um zu motivieren, ob ein Covector bei der Indizierung eine führende 1 benötigt oder nicht. Dies führt schnell zu Inkonsistenzen, wie in den vorherigen Beiträgen festgestellt.

Wie oben erwähnt, war ich noch nie ein Fan davon, mich auf nachlaufende Indizes zu verlassen, und konnte mir keine einzige Situation vorstellen, in der dies notwendig oder sogar wirklich nützlich ist. Aber ich kann akzeptieren, dass mein Standpunkt zu begrenzt ist.

Ich habe das alles noch nicht vollständig verdaut, aber meine Hauptfrage lautet einfach: Ist size(covector) gleich (n,) oder (1,n) ?

Wenn sie nicht zur AbstractArray -Familie gehören, ist es nicht einmal unbedingt erforderlich, dass size definiert wird.

Aus praktischen Gründen denke ich, dass es definiert wird (wie es für Zahlen usw. ist), und meine Stimme geht an (n,) . Aus Sicht des Lagers / Containers würde ich sagen, dass es keinen Unterschied zwischen Vektoren und Covektoren gibt. Es ist also auch nicht erforderlich, Covektoren als Container zu verwenden, und daher gehören sie nicht in die AbstractArray -Hierarchie. Es ist nur ein einfacher Wrapper-Typ, um auszudrücken, dass sie sich in Bezug auf lineare Algebra-Operationen anders verhalten als Vektoren.

"Solange Algebra und Geometrie getrennt wurden, waren ihre Fortschritte langsam und ihre Verwendung begrenzt. Wenn diese beiden Wissenschaften jedoch vereint sind, haben sie sich gegenseitig Kräfte verliehen und sind gemeinsam zur Perfektion marschiert." - Joseph Louis Lagrange

Ich wünschte, ich könnte mehr beitragen, aber ich dachte, ich würde nur sagen, dass ich für ein System bin, das es bevorzugt, die hoch entwickelten mathematischen Werkzeuge, die von Physikern und Ingenieuren verwendet werden, intuitiv zu bedienen und genau zu machen. Vielleicht könnte diese Ressource vom MIT von Nutzen sein ...

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

Wenn Sie darüber nachdenken, ist es fairer, das zu sagen

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

Dann hätten wir für die Matrixindizierung

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]

Derzeit gilt dies nicht ganz für Julia, z. B. erzeugt v[i, :] derzeit ein 1x1-Array und keinen Skalar. (Aber vielleicht sollte es nicht)
Die Assoziativität der Matrixmultiplikation in e_i' * M * e_j entspricht der Kommutativität des Schneidens entlang verschiedener Dimensionen M[:, j][i, :] = M[i, :][:, j] , was als wünschenswertes Merkmal erscheint.
Durch die obige Begründung sollten wir haben

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

@Jutho : Ich denke, dieses Paradigma "Indizieren als wiederholtes Schneiden" verallgemeinert sich auf höherdimensionale Arrays / Tensoren: Sie wenden ein Schneiden für jede Dimension auf den Index in beliebiger Reihenfolge an. Dies entspricht einer Reihe von Kontraktionen mit Tensoren erster Ordnung, die e_i usw. entsprechen, auch in beliebiger Reihenfolge (solange Sie verfolgen, welche Dimensionen übereinstimmen).

Ich denke, dieses Paradigma "Indizieren als wiederholtes Schneiden" verallgemeinert sich auf höherdimensionale Arrays / Tensoren: Sie wenden ein Schneiden für jede Dimension auf den Index in beliebiger Reihenfolge an. Dies entspricht einer Reihe von Kontraktionen mit Tensoren erster Ordnung, die e_i usw. entsprechen, auch in beliebiger Reihenfolge (solange Sie verfolgen, welche Dimensionen übereinstimmen).

Ja, ich bin mit Tensorkontraktionen usw. sehr vertraut, und tatsächlich entspricht das Erhalten eines Matrixelements / Tensorelements der Annahme eines Erwartungswerts in der Standardberechnungsbasis, dh der Kontraktion mit einigen Basisvektoren (solange Sie mindestens eine orthogonale Basis haben ), aber es gibt keine eindeutige Zuordnung, ob ein Index einer Kontraktion mit e_i oder mit e_i' . Dies entspricht der Entscheidung, ob der entsprechende Index in einer kovarianten oder kontravarianten Position erscheint, und es gibt keine eindeutige Entscheidung. Alle möglichen Kombinationen von oberen und unteren Indizes haben nützliche Anwendungen. Ein Vertrag mit e_i und e_i' ist jedoch nicht einmal die richtige Art, diese Frage aus mathematischer Sicht zu stellen, da es, wie oben erwähnt, tatsächlich keine mathematische Zuordnung wie gibt die Transponierung eines Vektors. Die Transponierung ist eine für lineare Karten (Matrizen) definierte Operation und entspricht auch dort nur dann der Matrixtransponierung, wenn Sie auch Doppelvektoren als Spaltenvektoren schreiben. Die Transponierung eines Vektors ist nur ein Trick, der in der Matrixalgebra eingeführt wurde (wobei Vektoren tatsächlich n x 1 Matrizen sind) und nur funktioniert, weil Sie sich in einem euklidischen Raum befinden, in dem es eine kanonische Abbildung aus dem (Konjugat) gibt ) Vektorraum zum dualen Raum.

Insbesondere wenn Sie die oben beschriebenen Eigenschaften möchten, sollten Sie haben, dass M[i,:] ein anderes Objekt zurückgibt (entweder eine 1xn Matrix oder einen Covector) als M[:,i] (was sollte) eine nx1 Matrix oder ein Vektor sein). Die Tatsache, dass dies nicht sauber auf höhere Dimensionen verallgemeinert wird, ist genau einer der Hauptdiskussionspunkte dieses Problems, und es scheint, dass die meisten Leute die APL-Indizierung befürworten, bei der mit einer Zahl indizierte Dimensionen gelöscht werden (dh beide M[:,i] und M[i,:] erzeugen ein Array mit Rang 1 (daher ein Vektor). Die Indizierung ist eine Eigenschaft von Arrays, und es ist diese Mischung des Indizierungsverhaltens mit linearen Algebraoperationen, die in erster Linie zu allen Verwirrungen / Inkonsistenzen führt. Es kann konsistent sein, solange Sie sich im geschlossenen Ökosystem von Objekten mit Rang N=2 , dh alles ist eine Matrix, auch Vektoren und Zahlen, und Sie berücksichtigen niemals höherdimensionale Arrays.

Ich habe gerade auch festgestellt, dass M[i,:] nach meinen obigen Überlegungen einen Covector produzieren müsste, wie Sie sagen. Es scheint also, dass Covectors auf einer bestimmten Ebene grundsätzlich nicht mit der APL-Indizierung vereinbar sind. Wenn wir die APL-Indizierung bevorzugen (und ich mag es), stellt sich die Frage, wo die Grenze zwischen dieser Welt und der Welt, in der Covectors leben, gezogen werden soll. (Ich hatte gehofft, dass es möglich sein würde, die beiden zu versöhnen, vielleicht hatte der Rest von Ihnen bereits erkannt, dass wir das aufgeben müssen?)

Dieser Konflikt ist vielleicht nicht so überraschend, wenn Sie darüber nachdenken:

  • Bei der APL-Indizierung werden nur aussagekräftige, indizierbare Dimensionen im Ergebnis beibehalten. der Rest wird herausgedrückt.
  • Wenn wir das mit v' würden, hätten wir v' = conj(v) . Der Covector kann stattdessen so gesehen werden, dass er eine fehlende erste Dimension verfolgt.

Ich denke, ein guter Anfang wäre, die Covektoren so weit wie möglich einzuschränken und im Grunde nur Multiplikation, Addition / Subtraktion und linke Division zu definieren.

Die Idee scheint zu sein, sie nicht zu <: AbstractArray . Wäre es sinnvoll, wenn sie Untertypen eines neuen LinearOperator -Typs wären? (Was ich weiß, stand schon früher zur Diskussion, aber ich erinnere mich nicht ganz an die Schlussfolgerungen.)

Ich denke, dass das Fehlen einer Mehrfachvererbung oder einer Sprachkonstruktion für Schnittstellen (die beide diskutiert wurden) erfordert, dass bestimmte Konzepte nur durch eine "implizite" Schnittstelle implementiert werden, dh eine Reihe von Methoden, die definiert werden müssen. Iteratoren sind ein solches Konzept, lineare Operatoren wären wahrscheinlich ein anderes, das durch eine Reihe von Methoden und nicht durch eine Typhierarchie spezifiziert werden muss. Es wäre seltsam, einen abstrakten Typ LinearOperator und dann nicht Matrix als Subtyp davon zu haben.

@toivoh Dies ist eine wichtige Überlegung. Aus diesem Grund habe ich neue Funktionen wie row(M,i) und col(M,i) , um den i -ten Vektor oder Covektor aus einer Matrix zu extrahieren. Diese Funktionen sind nur für zweidimensionale Arrays M und für Personen gedacht, die sich für Matrixalgebra interessieren. Es mag auf den ersten Blick etwas weniger offensichtlich erscheinen als die Indexierung im MATLAB-Stil, aber insgesamt hilft die konzeptionelle Trennung der Idee eines Vektors und seines Duals / Transponierten / Covektors, die Dinge klarer zu machen. Im Fall der Quantenmechanik sprang ein ganzes Feld zu Diracs Bra-Ket-Notation, einfach weil diese Unterscheidung zwischen Vektor und Covektor für komplexe Vektoren so kritisch ist, und Diracs Notation macht dies offensichtlich. Ich hoffe, dass Julia dies auch tut und ein gutes Werkzeug für höherdimensionale Speicherarrays und höherdimensionale lineare Algebra ist! (Weil wir gierig sind, oder?)

Ich muss sagen, dass eine Person, die Erfahrung mit MATLAB hat und mit den meisten realen Matrizen vertraut ist (aber kein Hardcore-Fanatiker der linearen Algebra ist), zunächst möglicherweise nicht erkennt, warum das, wofür einige von uns argumentieren, wichtig ist, aber ich glaube es ist.

Ich habe das schon früher gesagt, aber ich werde es wiederholen: Aus meiner Sicht haben wir diese Prioritätenliste, wobei die Kausalitäten nach unten fließen:

(1) Arrays sind grundsätzlich Speichercontainer, die alle Julia-Benutzer verwenden werden, und wir wollen dafür die beste Semantik. Regeln im APL-Stil scheinen mir sowieso eine sehr gute Lösung zu sein. Auf der anderen Seite scheinen MATLAB-artige, mindestens zweidimensionale Arrays mit Annahmen mit nachlaufenden eindimensionalen Indizes nur unnatürlich und verwirrend zu sein, und wie Jutho sagte, können sie sogar zu schlampiger Programmierung führen, bei der Sie den Überblick nicht richtig behalten Dimension Ihres Arrays. Ich würde so weit gehen zu sagen, dass für den Vektor v der Code v[i,:] einen Fehler v eindimensional ist. Elementweise Operationen wie + und .* sind für jede Containerklasse nützlich, nicht nur für Matrix- oder multilineare Algebra. Die einzigen Zugeständnisse, die Leute für die folgenden Dinge machen, sind die zusätzlichen Punkte auf .* usw.

(2) Die meisten, aber nicht alle Julia-Benutzer verwenden Matrixalgebra, daher fügen wir für einige Vektor- und Matrixoperationen etwas syntaktischen Zucker hinzu, meistens mit dem Symbol * (aber möglicherweise haben wir eine Matrixteilung usw.). In diesem System nehmen wir an, dass ein- und zweidimensionale Arrays Spaltenvektoren bzw. Matrizen sind. Für ein voll funktionsfähiges Matrixsystem benötigen wir auch Zeilenvektoren (Covektoren) und eine Transponierungsoperation ' . Ein Zeilenvektor ist in jeder Hinsicht ein eindimensionales Array, und ich behaupte, dass er als solches indiziert werden sollte (und sicherlich nicht wie covec[1,i] !!). Mit den APL-Regeln sind wir gezwungen, einige zu erstellen Die Unterscheidung zwischen Vektoren und Covektoren und das Typensystem ist dafür ideal (denken Sie daran, wir hatten das Glück, dass wir Matrix und Vektor bereits als Typealiasen für ein- und zweidimensionale Arrays anstelle eines Wrapper-Typs wiederverwenden konnten ... im Prinzip konnten wir dies wickeln Sie sie auch ein, aber ich verstehe den Punkt nicht). Mit dem Typsystem kann der Compiler herausfinden, dass CoVector * Vector skalar und Vector * CoVector eine Matrix ist und so weiter. Wie Toivoh sagte, verfolgt der CoVector aus MATLAB-Sicht genau die "fehlende" erste Dimension. Zufällige Benutzer müssen diese Objekte nicht erstellen. Sie geben nur Vektoren und Matrizen ein und verwenden die Operationen * und ' . Menschen, die sich darum kümmern, werden die Unterscheidung bemerken und schätzen. Die größte Änderung für Benutzer ist die Notwendigkeit, M[i,:] zu ändern, um eine neue Funktion row(M,i) oder in die Wrapper-Klasse mit etwas wie Transpose(M[i,:]) oder M[i,:]' - zu konvertieren. Man kann sich das als Vorteil vorstellen, denn wie bei der Dirac-Notation vergisst man nie , welche Objekte Vektoren und welche Covektoren sind, und die Zuordnung zu Objekten mit Typ-Asserts führt gegebenenfalls zu Fehlern. Ich denke, es lohnt sich jedoch, über diese Notation zu diskutieren. In Zukunft können wir das Wrapper-System sogar erweitern, um eine effiziente verzögerte Bewertung zu ermöglichen. Soweit ich sehen kann, ist es eine Win-Win-Situation für alle.

(3) Einige von uns interessieren sich für multi-lineare Algebra und mussten den Unterschied zwischen einem dualen Raum und einer Transposition usw. lernen. Jutho hat dies angesprochen. Julias Arrays sind bereits großartige Speichergeräte für höherdimensionale Tensoren, und zusätzliche Pakete (die möglicherweise den Weg in die Basis finden oder nicht) werden von Leuten wie uns verwendet. Wir brauchen und wollen keine Operationen wie ' oder * die für Arrays mit einer Dimensionalität von mehr als zwei definiert sind. Ich kann keinen speicherorientierten Anwendungsfall für ' , der durch explizite Neuordnung der Indizes nicht sauberer ausgeführt werden kann. Die ganze Idee, eindimensionale Indizes zu verfolgen, macht das Konzept nur weniger attraktiv. Bitte behalten Sie ' für ein- und zweidimensionale Arrays - wie MATLAB :) (siehe, ich habe nette Dinge zu tun sag über MATLAB ...)

Für ein voll funktionsfähiges Matrixsystem benötigen wir auch Zeilenvektoren (Covektoren) und eine Transponierungsoperation.

Diese Aussage bringt das Problem auf den Punkt. Indizierung, Umsetzung und * Produkte sind alle miteinander vermischt. Darüber hinaus ist es nicht möglich, die vollständige Semantik von Zeilenvektoren mit denen von Covektoren in Einklang zu bringen, ohne eine Funktion wie row(A, i) einzuführen, um die i -te Zeile von A als zurückzugeben Covector statt einer eindimensionalen Anordnung. Derzeit möchte A[1, :] sowohl "die erste Reihe von A nehmen" als auch "die erste Scheibe entlang der zweiten Dimension von A nehmen" bedeuten, aber diese Begriffe sind im Covector-Vorschlag grundsätzlich nicht kompatibel.

Ein Zeilenvektor ist in jeder Hinsicht ein eindimensionales Array, und ich behaupte, dass er als solcher indiziert werden sollte.

Ich glaube, Sie sind mit dieser Aussage ein wenig zu unzufrieden. Die vorangegangene Diskussion hat ganz klar gezeigt, dass ein Zeilenvektor nicht den gleichen Typ wie ein eindimensionales Array haben kann, wenn Sie eine vollständige lineare Algebra-Semantik (mit den richtigen Produkten und Transponierungen) wünschen. Erstens sollte die Indizierung in einen Covector komplexe konjugierte Werte zurückgeben, (v')[1] = conj(v[1]) , um die Konsistenz des inneren Produkts dot gewährleisten. Zweitens sind Covektoren keine Vektoren, sondern lineare Funktionale, die darauf warten, ein inneres Produkt mit einem Vektor zu erzeugen. Drittens haben Vektoren und Covektoren unter hcat und vcat unterschiedliche Verhaltensweisen hcat Verkettung von Arrays. Aus all diesen Gründen kann ein Zeilenvektor nicht "in jedem möglichen Sinne ein eindimensionales Array" sein.

Ich würde es befürworten, nachfolgende Singletons loszuwerden: Ich glaube nicht, dass ich jemals Julia-Code gesehen habe, der davon Gebrauch gemacht hat. Ich wollte ursprünglich sagen, dass wir es für 0-dimensionale Arrays benötigen würden, aber ich sehe, dass X[] funktioniert.

Ich denke, dass die APL-Indizierung zusammen mit der row(M,i) -Funktion für Zeilenvektoren am sinnvollsten ist und als guter Kompromiss erscheint. Ich bin mir bei der Indizierung von Zeilenvektoren nicht sicher, aber ich mag (v')[1,i] .

Die andere große Entscheidung, die wir noch nicht einmal angesprochen haben, sind Typen. Meine einzige Erkenntnis, die ich früher ausprobiert habe, ist, dass es wirklich schwierig ist, v' ein AbstractVector , da dies den Versand zu einem Chaos macht.

Zwei mögliche Optionen:

  1. Wir verwenden separate Typen für Matrix und Vektor:

    • Transpose <: AbstractMatrix

    • CoVector <: Any

    • eine Art Transponierung für Factorization Objekte.

  2. Wir verwenden für alle Transponierungen den gleichen Typ, haben aber X' _not_ ein AbstractMatrix

    • Transpose <: Any

Zur Konjugation können wir entweder

ein. Definieren Sie ConjugateTranspose (zusammen mit ConjugateCoVector wenn wir Option 1 oben wählen).

b. Verwenden Sie einen Conjugate Wrapper-Typ und verschachteln Sie ihn entsprechend: Wir benötigen eine Konvention, ob wir Transpose{Conjugate{T}} oder Conjugate{Transpose{T}} .

Ich mag es, wenn Transpose{Matrix} kein Subtyp von AbstractMatrix . Ich denke, das nächste Analogon, das wir in der Basis haben, ist ein spezieller Matrixtyp wie Symmetric , der sich algebraisch wie eine Matrix verhält, aber nicht in seiner Indizierungssemantik. (# 987 stellte fest, dass die Typhierarchie ohne Mehrfachvererbung oder heilige Merkmale die Containersemantik gegenüber der algebraischen Semantik berücksichtigen muss.)

Das Problem, Typen zu erstellen, die im Grunde genommen "semantische Tags" sind, tauchte auch in # 8240 auf. Ich denke, Transpose{Conjugate{T}} wäre vorzuziehen, da die Konjugation ein Begriff ist, der sich auf das zugrunde liegende Feld der Elemente auswirkt.

Hier sind Beispiele, die manchmal der gleichen nachfolgenden Singleton-Regel wie MATLAB folgen und manchmal nicht:

  • Nachfolgende Singletons sind bei Indizierungsvorgängen zulässig. (Wie MATLAB)
julia> (1:5)[5,1,1,1,1,1,1]
5
  • Nachfolgende Slices sind bei Indizierungsvorgängen nicht zulässig. (Im Gegensatz zu MATLAB, das es ihnen erlaubt.)
julia> (1:5)[5,:]
ERROR: BoundsError()
 in getindex at abstractarray.jl:451
  • Bei Arrays mit Rang> = 3 werden implizite nachfolgende Singletons zu einer indizierten Zuweisungsoperation hinzugefügt, wenn weniger Indizes als der Rang des Arrays vorhanden sind und der letzte Index ein Skalar ist (wie 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
  • Für Arrays mit Rang> = 3 werden implizite nachfolgende _Slices_ zu einer indizierten Zuweisungsoperation hinzugefügt, wenn weniger Indizes als der Rang des Arrays vorhanden sind und der letzte Index nicht skalar ist (wie 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
  • Bei Arrays mit Rang> = 3 werden einer Indizierungsoperation implizite nachfolgende Singletons hinzugefügt, wenn weniger Indizes als der Rang des Arrays vorhanden sind und der letzte Index ein Skalar ist (wie 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
  • Für Arrays mit Rang r> = 3 linearisiert eine Indizierungsoperation, wenn k <r Indizes als der Rang des Arrays vorhanden sind und der letzte Index ein Slice ist, implizit das verbleibende Array mit Rang rk. (wie 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
  • Nachfolgende Singletons werden bei Zuweisungsoperationen nicht gelöscht. (Im Gegensatz zu 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 hängt nicht automatisch eine nachfolgende Singleton-Dimension an, wenn dies eine gültige Operation ermöglicht. (Im Gegensatz zu Matlab, das tut)
julia> 1/[1.0,] #In MATLAB, interpreted as the inverse of a 1x1 matrix
ERROR: `/` has no method matching /(::Int64, ::Array{Float64,1})
  • Äußere Produkte funktionieren und sind eine Ausnahme von der vorherigen Regel. Ihre Semantik verwendet implizit einen nachgestellten Singleton im ersten Argument. (Wie 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

Es scheint, dass diese Dinge ein bisschen aufgeräumt werden könnten, wenn wir erst einmal entschieden haben, wie wir wollen, dass sie sich verhalten. Das aktuelle Verhalten bei der Indizierung mit zu wenigen Indizes, bei denen der letzte ein Slice ist, scheint besonders faul zu sein.

Beeindruckend. Die Beispiele, die Jiahao vorbringt, sind außerordentlich beunruhigend ... Ich mag es nicht, Singletons bei Indexierungsoperationen und Indexierungszuweisungsoperationen zu verfolgen, weil sie implizit und mehrdeutig sind. Wenn Sie sich dieser Verhaltensweisen nicht im Voraus bewusst sind, können Sie am Ende eine Sache tun, wenn Sie tatsächlich versuchen, eine andere zu tun. Ich bin dafür, mich zu einer genauen und klaren Verwendung der Sprache zu verpflichten und mehrdeutige Abkürzungen zu vermeiden.

Um dies tatsächlich umzusetzen, benötigen wir eine Art dreieckigen Versand, andernfalls bin ich mir nicht sicher, wie Sie Dinge wie "eine Matrix mit Complex64 oder Complex128 Einträgen, deren Transponierung ausdrücken würden oder seine konjugierte Transponierung ". Vor allem, wenn wir die Optionen 2 + b oben verwenden.

@ esd100 , welche davon sind mehrdeutig oder gefährlich? Dies alles ist entweder praktisch - wenn "virtuelle Singletons" für Sie vorgestellt werden und Sie sie wollten - oder unpraktisch - wenn Sie sie wollten und sie es nicht waren. Keines davon hat zwei plausible Bedeutungen mit unterschiedlichem Verhalten.

Ein Zeilenvektor ist in jeder Hinsicht ein eindimensionales Array, und ich behaupte, dass er als solcher indiziert werden sollte.

Ich glaube, Sie sind mit dieser Aussage ein wenig zu unzufrieden.

Wahr! Sorry @jiahao , ich werde den Jetlag beschuldigen. Das Wort, das ich schreiben wollte, ist "Tensor", und ich bezog mich streng auf das Verhalten beim Indizieren von [] . Sie haben Recht, dass die Verkettung eine wichtige Überlegung ist, und ich denke, dass die natürliche Vorgehensweise (zum Erstellen einer Matrix) ziemlich offensichtlich ist (wenn Sie sie in diesem Fall als 1 xn groß betrachten). Natürlich ist ein Covector nicht "in jeder Hinsicht" ein 1D Julia Array ... das ist der springende Punkt ...

Die Beispiele von Jiahao stören mich auch. Ich würde es befürworten, ein mögliches nachfolgendes Singleton-Verhalten zu entfernen. Das Linearisieren späterer Dimensionen kann nützlich sein, aber auch eine Art Faulheit fördern, bei der Sie vergessen, wie viele Dimensionen Ihr Array hat ... (Ich denke, dies wird durch "mehrdeutig" und "gefährlich" impliziert ... Ich möchte wirklich Julia, um einen Fehler auszulösen, wenn ich ein 16-dimensionales Array wie ein 15-dimensionales Array behandle.

Welche davon sind mehrdeutig oder gefährlich?

Der vierte erscheint mir sowohl zweideutig als auch gefährlich.

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

Dies sollte (für mich) eindeutig ein Syntaxfehler sein. A ist Rang 3 und die 3. Dimension wurde überhaupt nicht referenziert. Es ist sehr wahrscheinlich, dass die dritte Dimension durch einen Fehler weggelassen wurde und ein schwer zu findender Fehler in den Code eingefügt wurde. Ich wäre dankbar, wenn ich an seiner Stelle einen Fehler bekommen würde (wie sowohl in IDL als auch in fortran).

Ich denke, es ist am besten, wenn Arrays nur die Indizierung mit der richtigen Anzahl von Indizes oder den Sonderfall der linearen 1D-Indizierung zulassen (da dies äußerst praktisch ist). Dies würde auch das Nichtzulassen von nachfolgenden Singletons einschließen.

oder der Sonderfall der linearen 1D-Indizierung (da dies äußerst praktisch ist)

Wie leicht werden unsere Prinzipien verkauft! :) :)

Ich habe die lineare Indizierung noch nie so sehr gemocht. es kommt mir wie ein Hack vor. Oft wollen wir nur den schnellsten Weg, um über ein Array zu iterieren. Und für alle außer einfachen dichten Arrays kann die lineare Indizierung sehr langsam sein. Das heißt, wir sind möglicherweise nicht in der Lage, die lineare Indizierung vollständig zu eliminieren (aus Leistungsgründen und um zu viel Code zu brechen).

Ja, das stimmt. Mindestens 1D-Indizierung ist jedoch visuell unterschiedlich. Während das Indizieren eines 6D-Arrays mit 5D viel weniger ist. In jedem Fall hätte ich lieber strengere Indizierungsregeln und würde die lineare 1D-Indizierung aufgeben, als in die andere Richtung zu gehen. Zumal man leicht einen umgeformten Verweis auf das Array erhalten kann, das sich den Speicher teilt.

Können wir {} Klammern (oder andere) für die lineare Indizierung verwenden und [] für die mehrdimensionale Indizierung behalten? Nur eine Idee...

Am 23. März 2015, um 14.36 Uhr, schrieb Bob Portmann [email protected] :

Ja, das stimmt. Mindestens 1D-Indizierung ist jedoch visuell unterschiedlich. Während das Indizieren eines 6D-Arrays mit 5D viel weniger ist. In jedem Fall hätte ich lieber strengere Indizierungsregeln und würde die lineare 1D-Indizierung aufgeben, als in die andere Richtung zu gehen. Zumal man leicht einen umgeformten Verweis auf das Array erhalten kann, das sich den Speicher teilt.

- -
Antworten Sie direkt auf diese E-Mail oder sehen Sie sie auf GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -84805310 an.

Ich denke auch, dass es schön wäre, wenn wir die Syntax trennen könnten
für die lineare Indizierung aus der regulären Indizierungssyntax, aber ich stimme dem zu
könnte eine enorme Veränderung sein.

Während dieses Problem viele Debatten anzuregen scheint, wurde der größte Teil der eigentlichen Arbeit zur Verbesserung der Indizierung in einer ziemlich großen Anzahl von Pull-Anfragen während des gesamten 0,4-Zyklus geleistet. Jetzt, da wir zum Beispiel CartesianIndex und Freunde haben, verstehe ich nicht, warum man die Syntax für die lineare und kartesische Indizierung trennen müsste - tatsächlich können Sie sie jetzt kombinieren (# 10524). Ich möchte auch die lineare Indizierung loswerden, aber manchmal ist sie leistungsfähiger (wahrscheinlich hauptsächlich aufgrund von # 9080; enumerate und zip leiden unter demselben Problem). Wir sollten wahrscheinlich fastindex als Wrapper um eachindex implementieren, wie in # 10507.

Wenn Sie daran interessiert sind, Regeln zu indizieren, konzentrieren wir uns auf die interessanteste Grenze, anstatt dieses Problem zu Tode zu schlagen. In diesem Moment ist das zweifellos # 10525. Insbesondere https://github.com/JuliaLang/julia/pull/10525#issuecomment -84597488 benötigt eine Lösung.

@timholy Ich habe nicht wirklich alle Entwicklungen im Bereich der schnellen kartesischen Indizierung verfolgt, und nachdem ich mir gerade base / multidimensional.jl angesehen habe, sehe ich, dass eine Menge Metaprogrammierung im Gange ist. Gibt es eine Chance, dass Sie Zeit haben, darüber zu schreiben (oder einen JuliaCon-Vortrag zu halten), wie das alles funktioniert?

In mancher Hinsicht gibt es nicht viel zu wissen: Während unter der Haube eine ganze Menge los ist, besteht die Idee darin, die Verwendung schmutzig einfach zu gestalten. Also ganz wörtlich

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

könnte alles sein, was Sie wissen müssen. (Mit anderen Worten, die Hilfe zu eachindex könnte die gesamte erforderliche Dokumentation sein.) Es stellt sich jedoch heraus, dass Sie mit einigen Erweiterungen dieses grundlegenden Paradigmas eine große Anzahl von Algorithmen schreiben können. Was diese Erweiterungen sind und warum sie mächtig sind, kann in der Tat weniger offensichtlich sein.

Ich habe vor, dies irgendwann in den kommenden Monaten aufzuschreiben, aber wenn die Leute wirklich die Details wissen wollen, wäre vielleicht ein JuliaCon-Vortrag vernünftig. @Jutho oder @mbauman könnten diesen Vortrag genauso gut halten wie ich.

Ich würde mehr über Mehrdeutigkeit hinzufügen, aber ich denke, einige der nachfolgenden Diskussionen haben bestimmte Schlüsselpunkte zusammengefasst. Vielleicht bin ich als Außenseiter oder aus Angst empfindlicher für diese Art von Zweideutigkeit, weil ich das Risiko habe, zu dramatisch zu klingen. Meiner bescheidenen Meinung nach könnte eine beliebige Anzahl einfacher, unternehmenskritischer Gedankenübungen Sie auf einen Weg führen, auf dem sich die Kosten-Nutzen-Analyse eines einfachen Fehlers nicht lohnt.

Wir sollten wahrscheinlich fastindex als Wrapper um jeden Index implementieren, wie in # 10507

@timholy Gibt es einen Grund, warum eachindex keine UnitRange für schnelle lineare Arrays zurückgibt? Es wäre immer noch typstabil, und wenn der Aufrufer jemals sicherstellen möchte, dass er einen CartesianIndex erhält, kann er manuell einen CartesianRange erstellen (wir könnten auch eine Methode für eachindex(size(A)) hinzufügen, da CartesianRange nicht exportiert wird ).

Das hat es zuerst getan, aber ich denke, @Jutho hat es geändert. (Wenn das nicht stimmt, habe ich es wahrscheinlich geändert, ohne es zu merken.) Ich gehe davon aus, dass die Änderung aus Gründen der Konsistenz motiviert war (Sie können sich also darauf verlassen, dass Sie ein CartesianIndex ), was einen gewissen Sinn hat . Sie weisen jedoch darauf hin, dass es alternative Mittel gibt, um dies sicherzustellen.

@ Jutho , irgendwelche Gedanken?

Ich erinnere mich nicht, eachindex , aber es könnte sein. Ich erinnere mich sicherlich nicht an einen guten Grund dafür, außer ein konsistentes Ergebnis unabhängig von der Art des Arrays. Wenn der Unterschied zwischen Arrays mit und ohne effizienter linearer Indizierung in den Dokumenten klar erläutert wird, sehe ich keinen Grund, warum eachindex bei Arrays mit effizienter linearer Indizierung keinen linearen Bereich zurückgeben konnte.

@timholy , als Antwort auf Ihren vorherigen Beitrag kann ich nicht an JuliaCon teilnehmen. Sie können also gerne über das CartesianIndex Zeug sprechen (ich habe sowieso nur ein paar letzte Details beigetragen). Ich freue mich schon auf die Berichte und Videos der Konferenz.

Ein skurriler Gedanke: Für ein Array A einer beliebigen Dimension (einschließlich Dimension> 2) könnte man A' die Indizes von A zyklisch permutieren lassen. Also A'[i, j, k] == A[j, k, i] . Dies würde sich auf die übliche Matrixtransponierung für 2d-Arrays sowie auf die übliche Transponierung für Zeilen- und Spalten- "Vektoren" reduzieren, wenn sie wie in MATLAB ausgelegt wird (dh als [n, 1] bzw. [1, n] 2d-Arrays). Somit würde es niemals einen 2d-Spalten- "Vektor" einem wahren Covektor oder einen 2d-Zeilen- "Vektor" einem wahren Vektor zuordnen. (Ich denke, diese Eigenschaft ist gut, aber andere sind möglicherweise anderer Meinung.) Sie würde die Identitäten A'' == A' und v'' == v _ für Matrizen und Vektoren angeben, die als Array-Objekte_ ausgelegt sind. Angesichts der oben diskutierten Art der Typhierarchie (bei der echte Vektoren und Covektoren ausreichend von abstrakten Arrays unterschieden werden) könnte ' für echte Vektoren und Covektoren immer noch eine völlig andere Methode erhalten, wenn sie dem linearen algebraischen Konzept entspricht und muss nicht v'' == v befriedigen (könnte aber, wenn die Leute sich dafür entschieden haben).

Um es klar auszudrücken: Ich denke nicht für eine Sekunde, dass ' _ eine Methode für Arrays mit einer Dimension> 2 benötigt, aber ich hatte diesen Vorschlag oben nicht gesehen (es sei denn, dies war mit "Umkehren von Indizes" gemeint ") und dachte, ich würde es nur erwähnen. Der größte Schaden, den ich sehe (auf den ersten Blick), ist konzeptionell: eine (leicht willkürliche) kombinatorische Operation mit einem Operator zu verbinden, der normalerweise aus dem Bereich der linearen Algebra stammt. Darauf kann man antworten, dass zumindest ein gewisser Grad einer solchen Verschmelzung unvermeidlich ist, wenn wir versuchen, lineare algebraische Konzepte auf rein datenzentrierte Konzepte auszudehnen (wie aus dieser ganzen Diskussion hervorgeht). Als solches können wir auch eine bequeme Abkürzung für zyklische Permutationen mehrdimensionaler Arrays ernten, um zu bestimmen, was ' im Allgemeinen für mehrdimensionale Array-Dimensionen tun sollte, solange es sich auf den erwarteten Fall in reduziert 2 Dimensionen. Man könnte sogar eine Methode hinzufügen, die ein ganzzahliges Argument verwendet, so dass A'(k)[I] die Indizes von A[I] k zyklisch permutiert, wobei A'(ndims(A))[I] == A[I] und A'(-k)[I] die Indizes permutieren In die andere Richtung.

Nur ein Gedanke.

Wenn transpose für mehrdimensionale Arrays definiert ist, würde ich es vorziehen, dass es immer noch A''=A erfüllt, dh es ist seine eigene Umkehrung. Dies steht in direktem Widerspruch zum vorherigen Vorschlag.

Das ist fair. Wie gesagt, mein Vorschlag ist nur (potenziell) attraktiv, wenn man es sich bequem macht, eine Kluft zwischen der linearen algebraischen Bedeutung der Transponierung und der Array-zentrierten Bedeutung, die man im Fall d> 2 auferlegt, wachsen zu lassen. Meine Argumentation war, dass es eine gute Abkürzung für die Permutation von Indizes sein könnte, solange diese Kluft bereits (irgendwie) besteht und wenn die Methoden sonst nur völlig unbenutzt wären - solange es keine erfordert spezielle Behandlung, damit der Fall d = 2 funktioniert. Wie Sie (@Jutho) erwähnt haben, ist es ein bequemer Zufall, dass das Transponieren von Dimensionen im 2D-Fall die lineare algebraische Bedeutung hat (und dies erst, nachdem (Co-) Vektoren als 2D-Arrays identifiziert wurden). Vielleicht tun wir es also nicht ‚t Notwendigkeit , wählerisch zu sein über transpose ‘ mathematische Eigenschaften s (zB erfordert A'' == A ) für d> 2. Will man diesen Weg zu gehen, gibt es eine Menge von potentiell nützlichen Methoden , die konnten zugewiesen werden, zB: A' zyklisch einmal permutiert, A'(k) zyklisch permutiert k A'(I) , I::Array{Int, 1} mit der Länge <= ndims(A) zyklisch permutiert die in I aufgeführten Indizes und A'(p) für p::Permutation der Länge <= ndims(A) permutiert Indizes gemäß p . Aber ich nehme an, das Beste ist, ein Paket zu erstellen und zu prüfen, ob es nützlich genug ist, um sich durchzusetzen.

Ich unterstütze zwei Änderungen / Klarstellungen, die zB von StefanKarpinski am 16. Oktober 2014 und simonbyrne am 22. März 2015 erwähnt wurden, jedoch mit einer Bedingung:

  1. transpose sollte nur für Vektoren und zweidimensionale Matrizen verwendet werden, nicht für allgemeine Tensoren.
  2. Die Operatoren * und transpose sollten nachfolgende Singleton-Dimensionen am Ende der Berechnung löschen. Andernfalls können nachfolgende Singleton-Dimensionen beibehalten werden.

Diese beiden Änderungen würden viele Annehmlichkeiten bieten und viele Unklarheiten innerhalb der traditionellen linearen Algebra lösen. Sie sagen absichtlich nichts über die allgemeine Array-Indizierung oder Tensoralgebra aus, was separat betrachtet werden kann. (Insbesondere sollte die Tensor-Transponierung als eine völlig separate Operation von der Matrix / Vektor-Transponierung betrachtet werden.)

Nach diesem Vorschlag würde es bei der Arbeit mit Matrixmultiplikation und -transponierung im Wesentlichen keine Unterscheidung zwischen einem Vektor und einer einspaltigen Matrix geben, und es würde auch keine sinnvolle Unterscheidung zwischen einem Skalar, einem Vektor der Länge 1 und einem 1-By geben -1 Matrix. x' wäre jedoch eine 1-mal-n-Matrix, kein Vektor.

Andererseits könnte zum Zweck des Speicherns von Daten ein Array beliebiger Größe konstruiert werden. Es würden keine Singleton-Dimensionen gelöscht, da die linearen Algebra-Konzepte der Multiplikation und Transponierung nicht beteiligt wären.

Unten finden Sie eine hypothetische Abschrift einer Julia-Sitzung mit den vorgeschlagenen Änderungen.

Zunächst definieren wir einen Skalar, zwei Vektoren und eine Matrix.

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

Die Skalar-Vektor-Multiplikation funktioniert auch dann, wenn fremde Dimensionen vorhanden sind und das Ergebnis immer ein Vektor ist.

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

Transponieren ist eine 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

Das Multiplizieren einer Matrix mit einer einspaltigen Matrix ergibt ein Ergebnis, das mit dem Matrixvektorprodukt identisch ist.

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

Eine einzeilige Matrix mal eine Matrix entspricht einer einzeiligen Matrix.

julia> x'*A
1x3 Array{Float64,2}:
 30.0  36.0  42.0

Das innere Produkt ist ein Skalar und wird durch die allgemeineren Regeln für Matrixvektor- und Matrixmatrixprodukte erzeugt.

julia> x'*y
32.0

julia> x'*y[:,[1]]
32.0

Das äußere Produkt ist nichts Besonderes.

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

Ein Vektor mal ein Vektor ist ein Fehler.

julia> x*y
ERROR: `*` has no method matching *(::Array{Float64,1}, ::Array{Float64,1})

Ein Vektor mal eine Matrix ist ein Fehler.

julia> x*A
ERROR: DimensionMismatch("*")
 in gemm_wrapper! at linalg/matmul.jl:270
 in * at linalg/matmul.jl:74

Die Matrixmultiplikation ist assoziativ.

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

BEARBEITEN: Zwei Beispiele für die Herabstufung der inneren Dimension in einem Produkt wurden gelöscht. Sie hatten keinen großen Einfluss auf die aktuelle Diskussion.

Ich befürchte, dies würde die Typstabilität der beteiligten Operationen verlieren, da das Verwerfen nachfolgender Singleton-Dimensionen keine typstabile Operation ist.

So wie ich das sehe, geht es im gesamten Covector-Geschäft darum, eine Singleton-Dimension zu haben, die bekannt ist, basierend auf dem Typ, den sie entfernen wird. Wir haben uns auch sehr bemüht, die Unterscheidung zwischen einem Vektor und einer Matrix, die zufällig nur eine Spalte enthält, beizubehalten, aber dieser Vorschlag würde diese Unterscheidung in einigen Fällen aufheben, z. B. bei x'' .

Häretischer Vorschlag: Was wäre, wenn wir die Dimensionalität aus den Typparametern streichen und alle Dimensionalitäts- / Größenprüfungen zur Laufzeit durchführen würden? (Wir machen sowieso eine ganze Menge davon.) Und belassen Sie den Dimensionalitätsparameter für Typen, die auch die Größe als Typparameter codieren. (Ducks, versteckt sich zwei Wochen lang und ändert sein Github-Konto in @SomeoneWhoCertainlyIsntTimHolyUhUhNoWay.)

(Natürlich würde ich mich wirklich wegen # 10525 beschweren.)

FWIW, ich würde sagen, das ist keine so verrückte Idee: So funktioniert beispielsweise Torch, und die Torch-Entwickler haben Unzufriedenheit mit Julias Kodierung der Dimensionalität innerhalb des Typsystems geäußert.

@timholy Frage:

Wenn man immer noch daran interessiert wäre, CoVector / DualVector / TransposeVector müssten diese immer noch unsere neuen TimHolyArray{DataType} _ einpacken und wir müssten entweder einen Sinn daraus machen die Transponierung eines Arrays mit einer Dimension größer als zwei (oder eins) oder verbietet die Konstruktion TransposeVector(tharray) wenn tharray eine Dimension größer als zwei (oder eins) hat ... Tatsächlich alle Arten von Dinge müssen auf Laufzeitebene Fehler geben, die derzeit Fehler bei der Kompilierung sind (wie Multiplikationen, die derzeit undefiniert und daher vom Typsystem verboten sind).

Auf der anderen Seite kann die Implementierung eines Transponierungsflags in dieser neuen Klasse schlecht sein. Dies erhöht die Komplexität eines effizienten und leichten Containers. Ich würde diese Option eher ausschließen und die harte Arbeit dem Compiler / Typ-System überlassen.

Ich bin nicht unbedingt gegen Ihre Idee - aber es scheint ein zusätzliches Problem zu sein, das parallel zu Vektortransponierungen durchgeführt werden könnte.

@ Timholy : Ich bin mir wirklich sicher, ob dies der

Der Vorschlag war, dies auf alle Arrays anzuwenden. Aber ich bin jetzt gegen meinen eigenen Vorschlag, einfach weil Sie für viele mehrdimensionale Fälle N Schleifen für N dimensionale Arrays generieren müssen. Wir könnten das nicht mehr tun, wenn N kein Typparameter wäre.

Ja, ist das nicht der ganze Sinn Ihrer kartesischen Makros (oder die inszenierte Funktion, an die ich immer noch nicht gewöhnt bin :-))?
Ich habe ähnliche Dinge in C ++ gemacht, wo es ein echtes Problem ist, die Dimension als Vorlagenparameter zu haben. Aber ich hatte Situationen, in denen die dynamische Variante einschränkend war, weil man große if-Anweisungen benötigte, um die verschiedenen Array-Dimensionen zu spezialisieren.

Vorschlag:

  1. Benennen Sie das aktuelle Matrixmultiplikationsverhalten in timesfast und das aktuelle Transponierungsverhalten in transposefast .
  2. Ändern Sie (*) und transpose um nachfolgende Singleton-Dimensionen wie im vorherigen Kommentar abzuschneiden . Zum Beispiel würde u'*v ein Skalar werden, v'' würde ein Vektor werden und (v*v')/(v'*v) würde definiert werden.

Das vorhandene Verhalten ist typstabil. Das vorgeschlagene Verhalten folgt den Konventionen vieler linearer Algebra-Texte. Sie sind beide wertvoll. Vielleicht sollte Julia beides haben.

Ich möchte Julia im Klassenzimmer verwenden, daher stimme ich für das Standardverhalten, um Bequemlichkeit gegenüber Effizienz zu bewerten.

Vielen Dank an mehrere Mitwirkende, die mich über diesen sehr langen Thread auf dem Laufenden gehalten haben!

@briansutton : Ich denke, du solltest wirklich überdenken, wonach du

In dieser Ausgabe ging es darum, wie wir so viele Sonderfälle wie möglich vermeiden können.

Regeln, die das Fudgen von Singleton-Dimensionen ermöglichen, brechen in dem Moment zusammen, in dem eine der Singleton-Dimensionen eine ist, die Sie wirklich interessiert. Wenn v Regel "Alle abschließenden Singleton-Dimensionen abschneiden" die Regel v' ein Skalar, und daher ist v'' auch ein Skalar. Selbst in diesem Vorschlag gilt die Eigenschaft, dass v == v'' nicht immer gilt.

Sie können versuchen, die Regel so zu ändern, dass "nur die letzte nachfolgende Singleton-Dimension abgeschnitten wird, wenn das Array die Dimension 2 hat". Aber selbst mit dieser modifizierten Regel folgt das äußere Produkt v * w' nicht automatisch aus der Definition der Matrixmultiplikation, sondern muss eine eigene spezielle Definition von Vector * Matrix , und die Definition muss sei "wirf einen Fehler, es sei denn, die Formen sind (N) x (1, M)".

@jiahao :

Wenn ich lineare Algebra mit Vektoren und Matrizen mache, möchte ich keine Unterscheidung zwischen einem Skalar, einem 1-Element-Vektor und einer 1-mal-1-Matrix treffen. Derzeit erzeugen verschiedene Operationen eines der drei Objekte. Mein Vorschlag ist, für die Operationen der Matrixmultiplikation und -transponierung eine der drei als bevorzugte Darstellung auszuwählen.

In Bezug auf Ihr erstes Beispiel ( v==v'' ) möchte ich zunächst vermeiden, einen 1-Element-Vektor v zu konstruieren.

In Bezug auf Ihr zweites Beispiel denke ich, dass v*w' so behandelt werden sollte, wie Sie es beschreiben. Wenn ich mit Matrixmultiplikation und -transponierung arbeite, möchte ich, dass der Vektor v und die N-mal-1-Matrix v[:,[1]] dasselbe mathematische Objekt bezeichnen, auch wenn sie unterschiedliche interne Darstellungen haben. Dies würde einen speziellen Code erfordern, um die Matrix N-Vektor mal 1 mal M zu handhaben.

Ich denke, Sie sind immer noch nicht davon überzeugt, dass Typstabilität wichtig ist.

@briansutton : Ich denke, Sie würden es sehr informativ finden, die Maschinen zu implementieren, die erforderlich sind, damit Ihr Vorschlag so schnell ausgeführt wird, wie das aktuelle Typsystem die Ausführung von Julia-Code zulässt. Zum einen glaube ich, dass Ihre erklärten Ziele angesichts der julianischen Ziele, vorhersehbare Leistung und C-Speicherlayout-Kompatibilität zu liefern, nicht erreichbar sind.

Ich bin mir nicht sicher, ob ich die hin und her gehenden Argumente verstehe, aber eines ist mir aufgefallen. Vielleicht ist es Wunschdenken, aber es wäre schön, wenn der Computer so schnell denken könnte wie das menschliche Gehirn. Was ich meine ist, dass wenn Brian Sutton über das Programm "Auswahl einer bevorzugten Darstellung" spricht, ich mir ein Programm vorstelle, das genauso denken kann wie wir, wenn wir rechnen. Vielleicht ist es mit der heutigen Technologie nicht machbar und wird die Dinge zu sehr verlangsamen. Aber wäre es nicht schön ...

Ich bin Physiker und aktiver Benutzer von Julia.

Ich habe keine starke Vorliebe mehr dafür, alle nachfolgenden Singleton-Dimensionen wetterfest zu halten oder fallen zu lassen

Aber hier möchte ich ein eng verwandtes Thema ansprechen.

Die aktuelle Julia-Implementierung:

Sei V ein 3-dim Rang-1-Tensor (ein Vektor)
V [1] gibt uns einen Skalierer, keinen 1-dim-Rang-1-Tensor

Sei A ein 3x4x5 Rang-3-Tensor
B = A [1,:,:] gibt uns einen 1x4x5 Rang-3-Tensor.

Die beiden oben genannten Verhaltensweisen sind nicht ganz konsistent.

Ich bevorzuge die folgende Indizierungs- / Gleitfunktion:

Sei A ein 3x4x5 Rang-3-Tensor
B = A [1,:,:] gibt uns einen 4x5 Rang-2-Tensor.
C = A [1: 1,:,:] gibt uns einen 1x4x5 Rang-2-Tensor.
(Derzeit ergeben die beiden oben genannten das gleiche Ergebnis.)

Sei V ein Tensor vom Rang 1
B = V [1] gibt uns einen Skalierer
C = V [1: 1] ergibt einen 1-dim-Rang-1-Tensor.

Diese Funktion hilft uns, die Form des Tensors einfacher zu ändern, und ist hilfreich, wenn wir nachfolgende Singleton-Indizes zulassen.

Beste

Xiao-Gang


Von: esd100 [[email protected]]
Gesendet: Dienstag, 9. Juni 2015, 21:46 Uhr
An: JuliaLang / Julia
Cc: Xiao-Gang Wen
Betreff: Re: [julia] Vektortransponierungen ernst nehmen (# 4774)

Ich bin mir nicht sicher, ob ich die hin und her gehenden Argumente verstehe, aber eines ist mir aufgefallen. Vielleicht ist es Wunschdenken, aber es wäre schön, wenn der Computer so schnell denken könnte wie das menschliche Gehirn. Was ich meine ist, dass wenn Brian Sutton über das Programm "Auswahl einer bevorzugten Darstellung" spricht, ich mir ein Programm vorstelle, das genauso denken kann wie wir, wenn wir rechnen. Vielleicht ist es mit der heutigen Technologie nicht machbar und wird die Dinge zu sehr verlangsamen. Aber wäre es nicht schön ...

- -
Antworten Sie direkt auf diese E-Mail oder sehen Sie sie sich auf Gi tHubhttps an: //github.com/JuliaLang/julia/issues/4774#issuecomment -110554622.

Ich meine: C = A [1: 1,:,:] gibt uns einen 1x4x5 Rang-3-Tensor.

Xiao-Gang


Von: Xiao-Gang Wen [[email protected]]
Gesendet: Montag, 22. Juni 2015, 12:01 Uhr
An: JuliaLang / Julia; JuliaLang / Julia
Cc: Xiao-Gang Wen
Betreff: RE: [julia] Vektortransponierungen ernst nehmen (# 4774)

Ich bin Physiker und aktiver Benutzer von Julia.

Ich habe keine starke Vorliebe mehr dafür, alle nachfolgenden Singleton-Dimensionen wetterfest zu halten oder fallen zu lassen

Aber hier möchte ich ein eng verwandtes Thema ansprechen.

Die aktuelle Julia-Implementierung:

Sei V ein 3-dim Rang-1-Tensor (ein Vektor)
V [1] gibt uns einen Skalierer, keinen 1-dim-Rang-1-Tensor

Sei A ein 3x4x5 Rang-3-Tensor
B = A [1,:,:] gibt uns einen 1x4x5 Rang-3-Tensor.

Die beiden oben genannten Verhaltensweisen sind nicht ganz konsistent.

Ich bevorzuge die folgende Indizierungs- / Gleitfunktion:

Sei A ein 3x4x5 Rang-3-Tensor
B = A [1,:,:] gibt uns einen 4x5 Rang-2-Tensor.
C = A [1: 1,:,:] gibt uns einen 1x4x5 Rang-2-Tensor.
(Derzeit ergeben die beiden oben genannten das gleiche Ergebnis.)

Sei V ein Tensor vom Rang 1
B = V [1] gibt uns einen Skalierer
C = V [1: 1] ergibt einen 1-dim-Rang-1-Tensor.

Diese Funktion hilft uns, die Form des Tensors einfacher zu ändern, und ist hilfreich, wenn wir nachfolgende Singleton-Indizes zulassen.

Beste

Xiao-Gang


Von: esd100 [[email protected]]
Gesendet: Dienstag, 9. Juni 2015, 21:46 Uhr
An: JuliaLang / Julia
Cc: Xiao-Gang Wen
Betreff: Re: [julia] Vektortransponierungen ernst nehmen (# 4774)

Ich bin mir nicht sicher, ob ich die hin und her gehenden Argumente verstehe, aber eines ist mir aufgefallen. Vielleicht ist es Wunschdenken, aber es wäre schön, wenn der Computer so schnell denken könnte wie das menschliche Gehirn. Was ich meine ist, dass wenn Brian Sutton über das Programm "Auswahl einer bevorzugten Darstellung" spricht, ich mir ein Programm vorstelle, das genauso denken kann wie wir, wenn wir rechnen. Vielleicht ist es mit der heutigen Technologie nicht machbar und wird die Dinge zu sehr verlangsamen. Aber wäre es nicht schön ...

- -
Antworten Sie direkt auf diese E-Mail oder sehen Sie sie sich auf Gi tHubhttps an: //github.com/JuliaLang/julia/issues/4774#issuecomment -110554622.

Was Sie fragen, ist, wie slice derzeit funktioniert. Meiner Meinung nach wird A[stuff] ein Synonym für slice(A, stuff) , und Sie werden wahrscheinlich Ihren Wunsch bekommen.

Lieber Tim:

Danke für den Tipp. Ich habe Slice ausprobiert. Es passt nicht zu meinen Bedürfnissen. Slice erzeugt einen neuen Datentyp "Subarray", den ich in meinem anderen Code, der den Datentyp :: Array verwendet, nicht verwenden kann.

Vielleicht kann ich meinen anderen Code so ändern, dass er den Datentyp "Subarray" zulässt.

Xiao-Gang


Von: Tim Holy [[email protected]]
Gesendet: Montag, 22. Juni 2015, 17:32 Uhr
An: JuliaLang / Julia
Cc: Xiao-Gang Wen
Betreff: Re: [julia] Vektortransponierungen ernst nehmen (# 4774)

Was Sie fragen, ist, wie Slice derzeit funktioniert. Mein Gefühl ist, dass A [Zeug] ein Synonym für Slice (A, Zeug) wird, und so werden Sie wahrscheinlich Ihren Wunsch bekommen.

- -
Antworten Sie direkt auf diese E-Mail oder sehen Sie sie sich auf Gi tHubhttps an: //github.com/JuliaLang/julia/issues/4774#issuecomment -114268796.

Gibt es etwas, das wirklich davon abhängt, die konkreten Array -Typen anstelle von AbstractArray in Ihrem Code zu verwenden? Möglicherweise ist lediglich das Suchen / Ersetzen von Array durch AbstractArray erforderlich, damit die Dinge funktionieren.

Lieber Scott

Vielen Dank für den Tipp.

Xiao-Gang


Von: Scott P. Jones [[email protected]]
Gesendet: Donnerstag, 25. Juni 2015, 9:55 Uhr
An: JuliaLang / Julia
Cc: Xiao-Gang Wen
Betreff: Re: [julia] Vektortransponierungen ernst nehmen (# 4774)

Gibt es etwas, das wirklich von der Verwendung der konkreten Array-Typen anstelle von AbstractArray in Ihrem Code abhängt? Möglicherweise ist lediglich das Suchen / Ersetzen von Array durch AbstractArray erforderlich, damit die Dinge funktionieren.

- -
Antworten Sie direkt auf diese E-Mail oder sehen Sie sie sich auf Gi tHubhttps an: //github.com/JuliaLang/julia/issues/4774#issuecomment -115265047.

Hat das den Trick für dich getan? Ich helfe gerne!

@wenxgwen , alternativ können Sie copy(slice(A,...)) aufrufen, um eine Kopie des Slice in einem neuen Array , das dann nativ mit Ihren bereits vorhandenen Funktionen funktionieren sollte.

Die Behebung dieses Problems ist mittlerweile zu einem lukrativen Unterfangen geworden

Da die Behebung dieses Problems jetzt auf meinem Weg zu 3 Kommas von entscheidender Bedeutung ist ...
image

Aber in aller Ernsthaftigkeit. Ich habe versucht, die Diskussion vollständig durchzulesen, kann jedoch nicht garantieren, dass dies noch nicht vorgeschlagen wurde:

Können wir möglicherweise die AbstractArray-Definition erweitern, um ein zusätzliches Merkmal (ähnlich wie LinearIndexing?) Zu haben, das definiert, ob die zugrunde liegenden Daten zeilenbasierter oder spaltenbasierter Speicher sind? Dieser Zusatz würde die folgenden Eigenschaften eröffnen (bitte konzentrieren Sie sich nicht auf Namen ... nur Konzepte):

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)

Offensichtlich fehlen diesem Vorschlag viele Definitionen, aber vielleicht kann er die Diskussion beginnen. Können wir sowohl die Speicherung von Spaltenindizes als auch von Zeilenindizes gleichzeitig unterstützen und dabei einige Probleme "beheben"?

Ich wünschte sehr, Julia wäre zeilenbasierter Speicher, da ich so natürlich über Schleifen und viele andere Operationen denke. (und ich glaube nicht, dass ich der einzige bin, der so denkt) Kommentare bitte!

Es ist ein interessanter Gedanke, wenn der Konstruktor auch Zeilen oder Spalten berücksichtigt. Ich denke, es wurde schon einmal irgendwo tief im GitHub-Issue-System besprochen. Ich könnte falsch liegen!

Ich persönlich bevorzuge spaltenbasiertes Speichern, da die meisten meiner Lehrbücher Spalten für ihre Mathematik verwenden und ich dann in Julia nicht alles ändern muss, um stattdessen Zeilen zu verwenden. Ich fand es am Anfang auch seltsam, aber es wurde schnell zu einem Problem für meine Arbeit. Es gibt Algorithmen, die sich leichter in Zeilen ausdrücken lassen, weshalb dies hilfreich sein kann, wenn Sie bei Bedarf einen nicht standardmäßigen Speicher verwenden können müssen. Ich würde hoffen, dass die Konvention darin besteht, immer eine Spaltenhauptmatrix oder einen Spaltenvektor zurückzugeben, wenn Sie eine Funktion haben, die so exportiert wird, dass es nie eine Frage gibt, welchen Typ Sie beim Aufrufen einer Funktion haben. Andernfalls kann es ziemlich unordentlich werden und unangenehm werden, wenn Sie immer nachsehen müssen, welcher Typ zurückgegeben wird.

Spaltenbasiert oder zeilenbasiert fallen nicht in den Geltungsbereich dieses Problems.

@tbreloff Ich mag die Idee sehr. Es wäre großartig, eine einfachere Schnittstelle zu Sprachen / Bibliotheken zu haben, die Zeilenmajor sind.

Jiahao hat Recht, dass es nicht zum Thema gehört, Zeilenmajor gegenüber Spaltenmajor zu bevorzugen. Es ist
Nur ein netter Nebeneffekt, der das Transponierungsproblem auf "julianische" Weise löst
(parametrische Typen und inszenierte Funktionen) gibt den Menschen mehr Flexibilität in
Speicherformat.

Wenn Ihnen die Idee, Arrays ein Zeilen- / Spaltenmerkmal hinzuzufügen, nicht gefällt, dann ist das
Genau das Gleiche kann mit einem TransposeView {T, N} erreicht werden, aber ich
Ich vermute, es wird komplizierter sein, gut zu implementieren.

Der größte Nachteil des zusätzlichen Merkmals ist die Verwirrung neuer Benutzer.
und das fällt mir schwer zu versöhnen.

Am Samstag, den 26. September 2015, benachrichtigt Scott P. [email protected]
schrieb:

@tbreloff https://github.com/tbreloff Die Idee gefällt mir sehr gut. Es
wäre toll, einfacher mit Sprachen / Bibliotheken kommunizieren zu können
das sind Reihenmajor.

- -
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/JuliaLang/julia/issues/4774#issuecomment -143436947.

Verändert das Überladen von Anrufen den Entwurfsraum überhaupt? Zuweilen schien es oben eine Art Mehrdeutigkeit im Sinne von * . Insbesondere wenn wir möchten, dass v::Covector * w::Vector einen Skalar zurückgibt, nehmen wir * tatsächlich als "Map w unter v " anstelle der Matrixmultiplikation? Wenn ja, könnte man dann nicht in ähnlicher Weise verlangen, dass w::Vector * v::Covector einen Skalar zurückgibt, da Vektoren selbst lineare Karten über Covektoren sind?

Vielleicht wäre es stattdessen hilfreich, call zu überladen und v(w) zu schreiben, um die Operation "Karte unter v " für w und ähnlich für w(v) zu kennzeichnen

Wenn ja, könnte man dann nicht in ähnlicher Weise verlangen, dass w :: Vector * v :: Covector einen Skalar zurückgibt, da Vektoren selbst lineare Karten über Covektoren sind?

Ich denke, wir versuchen, den Matrix- und Vektorkonventionen zu folgen, die viele Menschen an der Universität im ersten Jahr sehen, die nicht kommutativ ist und eine Vektor * transponierte Vektor -> Matrix (von Rang 1) hat, dh nur einen (ungleich Null) Singular hat Wert). Was Sie geschrieben haben, klingt eher nach einem abstrakten linearen Algebra-Konzept, bei dem der Vektor und der Co / Dual-Vektor Karten voneinander zu einem Skalar sind, was in Ordnung ist, sich jedoch ein wenig von dem unterscheidet, was (IMHO) in Julia und MATLAB versucht wird. Ich denke, wir könnten das, was Sie geschrieben haben, als das innere Produkt interpretieren, dot() oder , das auf zwei Covektoren wirkt (die wir wahrscheinlich für Covektoren definieren sollten), aber manchmal wollen wir auch das äußere -Produkt und derzeit das nicht kummutative * ermöglicht es uns, beide Verhaltensweisen zu schreiben und auszudrücken.

Was die Aufrufkonvention v(w) betrifft, könnten wir für das Punktprodukt gleichermaßen sagen, dass wir den Betrag von v in Richtung w möchten, was darauf hindeutet, dass wir den Indexierungsoperator v[w] . (Übrigens, ich sage nicht, dass dies eine gute Option für das Sprachdesign ist - nur eine Beobachtung!)

Wir könnten auch für das Punktprodukt sagen, dass wir den Betrag von v in Richtung w was darauf hindeutet, dass wir den Indexierungsoperator v[w] .

Dieser Vorschlag entspricht fast, aber nicht der Semantik der Indizierung. Es kollidiert auch mit unserer aktuellen Unterstützung für die Vektorindizierung, wenn v ein Vector{<:Integer} .

Bedenken Sie, dass für v :: Vector{T<:Real} v[1] dem Punktprodukt v⋅e₁ , wobei e₁ der kanonische Basisvektor entlang der ersten Achse ist. Daher ist eine einfache Indizierung wirklich die Funktion

   v[n] : n :: Integer --> y = (v ⋅ eₙ) :: T

Für die Vektorindizierung erzeugt v[[1, 2]] [v⋅e₁, v⋅e₂] was das Ergebnis der Projektion von v in den von {e₁, e₂} überspannten Unterraum ist, oder äquivalent ist es das Ergebnis von v' * [e₁ e₂] .

Die Vektorindizierung ist also die Funktion

   v[I] : I :: Vector{<:Integer} --> y = v' * [eₙ for n in I] :: Vector{T}

Der Vorschlag, v[w] = (w ⋅ v) v zu machen, widerspricht dieser Definition, da die implizite Zuordnung von einer Sammlung von Indizes n (wie durch w ) zu einer Sammlung von kanonischen Basisvektoren eliminiert wird eₙ , was erforderlich ist, damit unsere aktuellen Indexierungsregeln funktionieren.

Ich denke, wir haben zwei Möglichkeiten, indem wir den Umfang vorerst nur auf die Vektortransponierung beschränken. Wir können entweder einen Fehler machen oder den speziellen Covector-Typ einführen. Angesichts der erstklassigen Natur des Vektors in Julia denke ich, dass Ersteres ein sehr schwieriger Verkauf wäre… und um dies konsequent zu tun, sollten wir Vektoren wahrscheinlich die Teilnahme an der Algebra der Matrizen vollständig verbieten.

Also habe ich auf den Covector geschossen. Es ist viel Arbeit und ich werde leider nicht mehr Zeit damit verbringen können. Ich poste es hier in der Hoffnung, dass jemand entweder damit rennt oder dass wir aus den Schwierigkeiten lernen und uns entschließen, wegzulaufen. Aber ich habe ein Zweiggebäude mit Transponierungen als Ansichten und einer mit Covektoren implementierten Matrixmultiplikation. Einige ausgewählte Tests bestehen, jedoch nur ohne depwarn=error (z. B. ./julia -e 'using Base.Test; include("test/matmul.jl")' ). https://github.com/JuliaLang/julia/compare/mb/transpose

Ich habe zwei neue Ansichtstypen definiert, die für transpose und ctranspose von Matrizen und Vektoren verwendet werden sollen:

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

Der Parameter C ist ein einfacher Boolescher AbstractArray . Dies ist eine ziemlich starke Voraussetzung, damit der Versand angemessen funktioniert.

Einige Nachteile:

  • Covectors sind ausgesprochen kompliziert und es wird eine Herausforderung sein, sie auf zugängliche und strenge Weise zu dokumentieren. Hier könnte die Sprache helfen - wir könnten sie einfach RowVector s nennen. Unabhängig davon ist die allererste Schwierigkeit, auf die Sie stoßen, wenn Sie versuchen, dieses Verhalten zu erklären, die Art und Weise, wie Sie über die Form eines Covektors sprechen ( size ist undefiniert). Wenn (m,n) die Form einer Matrix ist, kann (m,) die Form eines Vektors darstellen ... und wenn wir die Tupelnotation missbrauchen, können wir einen Covector mit der Form (,n) umgangssprachlich beschreiben

    • Matrix * Vektor ist (m,n) × (n,) → (m,)

    • Covector * Matrix ist (,m) × (m,n) → (,n)

    • Vektor * Covector ist (n,) × (,n) → (n,n)

    • Covector * Vektor ist (,n) × (n,) → α (skalar)

  • Die Anzahl der binären Operationen und Typen führt hier zu einer enormen kombinatorischen Explosion der Anzahl der Methoden, die definiert werden müssen. Nur zur Multiplikation haben wir:

    • Mutation: (mutierend, nicht mutierend)
    • Transponieren: (A, Aᵀ, Aᴴ) × (B, Bᵀ, Bᴴ).
    • Form: (Matte × Matte; Vec × Matte; Matte × Vec, Vec × Vec). Beachten Sie, dass nicht alle von allen Transponierungskombinationen unterstützt werden, die meisten jedoch.
    • Implementierung: (BLAS, Strided, generisch sowie Spezialisierungen für Strukturmatrizen)

    Während einige dieser Vorgänge gut mit mehreren Versand- und Fallback-Verhaltensweisen übereinstimmen, gibt es immer noch eine große Anzahl von Methoden für jeden Bediener. Das vollständige Entfernen der gemischten Matrix- / Vektorunterstützung hier würde definitiv dazu beitragen, die Dinge zu vereinfachen.


  • Sie lösen keine der Schwierigkeiten beim Löschen aller skalaren Dimensionen sofort. Die Unterstützung jeglicher Art von Vektortransponierung, wenn wir skalare Dimensionen fallen lassen, versetzt uns in Bezug auf das komplexe Konjugat in ein mehrdeutiges Gebiet (siehe https://github.com/JuliaLang/julia/pull/13612). Vielleicht könnten wir A[1,:] einen Covector zurückgeben lassen, aber es verallgemeinert sich nicht gut und es wäre ziemlich seltsam, ein Nicht-AbstractArray von einem Slice zurückzugeben. Es wäre großartig, wenn die Leute # 13612 ausprobieren und speziell nach Fällen suchen könnten, in denen die konjugierte Transponierung eines Vektors Probleme verursacht - es wäre genauso schlecht mit der aktuellen Vektortransponierungssemantik und erfordert keine Belichtung durch Covectors.

Einige Vorteile:

  • Die direkte Verwendung des Versands anstelle der speziellen Analyse auf Ax_mul_Bx ist ein großer Gewinn. Es lässt sich sehr gut mit der BLAS-API komponieren. Im Allgemeinen möchten Sie die Konjugation auf den innersten Ebenen der Algorithmen durchführen. Daher ist es viel sinnvoller, diese Informationen mit den Argumenten beizubehalten. Bei BLAS-Aufrufen kann eine einfache Funktion verwendet werden, um das Transponierungszeichen nachzuschlagen ( ntc ).
  • Die Multiplikation zwischen Vektoren und Covektoren ist jetzt assoziativ, da v'v einen Skalar zurückgibt. Sie können jetzt v'v*v von links nach rechts auswerten und die Bildung der Matrix vermeiden.
  • Dies ermöglicht das Entfernen der Vektor * -Matrix, was nur funktioniert, wenn Sie alle Vektoren so behandeln, als hätten sie nachfolgende Singleton-Dimensionen. Dies ist ein großer Schritt, um die Unterstützung für nachfolgende Singleton-Dimensionen im Allgemeinen vollständig zu entfernen.

Weitere Hinweise:

  • Die Komplexität der Transponierung, die durch einen Parameter vom Typ Boolean dargestellt wird, funktioniert in Ordnung, aber ich hatte oft das Gefühl, die Parameter in der falschen Reihenfolge definiert zu haben. Selten wollte ich tatsächlich sowohl T als auch C einschränken oder erfassen. Ich bin mir nicht sicher, ob es in Bezug auf die Anzahl der Platzhalter-Typevars, die Sie definieren müssten, besser wäre, wenn Sie es anders machen würden, aber zumindest würde es mit AbstractArray{T} übereinstimmen.
  • Dies verschärft die Schwierigkeiten bei StridedArray erheblich. Das Lesen der Methodentabelle für * ist bei Typen wie ::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}} eine ziemliche Herausforderung. Sicher, ich habe es als Typealias geschrieben, aber selbst das Verwalten all dieser verschiedenen Typaliasnamen ist ein Schmerz ( StridedMatOrTrans , QRCompactWYQorTranspose usw.).
  • Ich hatte keine Chance, dies in den SparseVector zu integrieren, aber das wird ein großer Gewinn sein, da es die Transponierung effizient darstellt, ohne dass eine CSR erforderlich ist.
  • Müssen wir eine skalare und / oder nicht skalare Indizierung für Covectors definieren? Wenn ja, was ist das Ergebnis von r[:] oder r[1:end] ? Ist es ein Vektor oder ein Covektor? Ich denke, ich würde versuchen, wegzukommen, ohne dies zu definieren, wenn wir können. Interessanterweise hat Matlab ganz spezielle Regeln für die Indizierung von Zeilenvektoren mit anderen Vektoren - sie bemühen sich sehr, ihre Zeilenzahl auf Kosten einiger seltsamer Eckfälle aufrechtzuerhalten ( r((1:end)') ist r(1:end) ist r(:)' ). Ich habe momentan eine skalare Indizierung in meinem Zweig definiert, aber vielleicht sollte diese auch entfernt werden. Das würde deutlich machen, dass der Covector nur mit linearen Algebraoperationen verwendet werden darf, die davon wissen.

Abschließend möchte ich nur darauf hinweisen, dass die meisten Vorteile, die ich hier sehe, genauso auf den Typ MatrixTranspose selbst zutreffen. Ich denke, dies zeigt, dass der Covector recht gut funktionieren kann, ohne zu tief in abstrakte Algebren einzutauchen, um gemischte Vektor / Matrix-Operationen zu ermöglichen. Es fügt definitiv eine nette Funktion hinzu (die Fähigkeit, Vektoren konsistent mit linearer Algebra zu verwenden), aber ich bin nicht sicher, ob es die zusätzliche Komplexität wert ist.

Wenn Sie sich aus Neugier noch an @mbauman erinnern, was hat Sie motiviert, den booleschen Parameter C einzuführen? Kannst du nicht einfach haben (in Pseudotraits)

transpose(::Matrix) -> MatrixTranspose
transpose(::MatrixTranspose) -> Matrix

? Ich bezweifle hier nicht Ihr (außergewöhnliches) Urteil, sondern möchte nur verstehen, welche Erkenntnisse Sie dazu gezwungen haben.

Vermutlich könnte man dies auf einen PermutedDimensionArray -Typ verallgemeinern, wobei ein Tupelparameter die Permutation codiert. Der Typ Covector offensichtlich einen anderen Zweck und muss eine separate Sache sein.

Sie benötigen eine Möglichkeit, um konjugierte Transponierungen zu verarbeiten (und auch nicht transponierte konjugierte Arrays für (A').' ). Ich sehe drei offensichtliche Möglichkeiten, dies zu tun:

  • Speichern Sie die Konjugation als boolesches Feld in nur einem MatrixTranspose -Typ. Nach dem Nachdenken denke ich, dass dies definitiv die beste Option für die Schnittstelle mit externem BLAS ist. In Bezug auf die native JuliaBLAS möchte ich jedoch sicherstellen, dass Julia / LLVM in der Lage ist, T.isconjugate ? conj(T[i,j]) : T[i,j] aus Schleifen zu heben.
  • Ein Typ mit einem Konjugationsparameter. Dies ist, was ich gewählt habe, und ich glaube, ich wurde von der Tatsache beeinflusst, dass wir derzeit Pseudo-Versand über die Konjugation über Ac_mul_Bt und Freunde durchführen. Es hat auch stärkere Garantien für die Optimierungen zum Entfernen von Zweigen, die Julia durchführen kann. Aber ich habe nicht lange darüber nachgedacht… Ich wollte nur eine Skizzenimplementierung starten und war mehr besorgt über den Covector.
  • Zwei separate Typen, MatrixTranspose und MatrixCTranspose . Isomorph zu einem Typparameter, aber ich finde zwei separate Wrapper-Typen ärgerlich. Abstrakte Supertypen und Vereinigungsaliasnamen können helfen, aber ich würde trotzdem den Parameter über dieser Option auswählen.

Ich nehme an, es gibt jetzt eine vierte Option mit typisierten Funktionen, bei der alle Transformationsfunktionen innerhalb des Transponiertyps gespeichert werden. Dazu ist jedoch auch ein Typparameter erforderlich, damit die Funktion schnell ist, und es handelt sich dann auch um einen Typparameter, der nicht einfach zu versenden ist.

Ich frage mich, ob wir einen ConjugateView Wrapper haben könnten, mit der Regel conj und transpose sicherstellen, dass ConjugateView in den MatrixTranspose Wrapper gelegt wird . Dh für A a Matrix ,
A' = conj(transpose(A)) = transpose(conj(A)) alle erzeugen ein MatrixTranspose{ConjugateView{Matrix}} (Löschen nicht informativer Typparameter).

Ah ja, das ist auch sinnvoll. Ich neige dazu, die konjugierte Transponierung als ein atomares "Ding" zu betrachten, also habe ich diese Option verpasst. Ich habe darüber nachgedacht, wie nicht konjugierte Transponierungen durch einen speziellen Subarray-Indextyp dargestellt werden können, genau wie Umformen.

Ich bin froh, dass ihr noch daran arbeitet! Dies ist ein epischer Thread! Drei mal hurra!!!

Ist das für 1.0 geplant?

In zwei Ausgaben (# 18056, # 18136) wurde ich auf diesen riesigen Faden hingewiesen.
Also probiere ich es aus.

Jetzt hat Julia echte eindimensionale Vektoren, z. B. mx[row,:] ist keine 1xn-Matrix mehr.
Dies ist eine willkommene Abwechslung!

Als Nebeneffekt wurden jedoch einige bestehende Probleme offensichtlicher.
Ich wurde von der Tatsache gebissen, dass v*mx für eindimensionale Vektoren nicht funktioniert.
Mathematisch sollte es natürlich funktionieren und einen 1-d-Vektor zurückgeben,
wenn das allgemeine a*b -Produkt durch Vertragsabschluss definiert wird
der letzte Index des ersten Terms und der erste Index des zweiten Terms.

Derzeit lautet die Methodensignatur des Vector-Matrix-Produkts:
(*)(A::AbstractVector, B::AbstractMatrix) = reshape(A,length(A),1)*B
und diese Methode wird für den Fall v*v' und nicht für v*mx .
(Danke @andreasnoack für den Hinweis.)
Offensichtlich kann nicht eine einzige Methode für beide verwendet werden.

Es scheint, dass Julia mit einigen Matlab-ähnlichen Coventions zu kämpfen hat.
Aber in Matlab gibt es keine 1-d-Vektoren, nur 1xn- und nx1-Matrizen.
So viele Dinge, die natürlich sind, können hier ein Problem sein.
Julia hat echte 1-D-Vektoren, was ein großer Vorteil sein sollte.
Es wäre schön, einen wirklich konsistenten eigenen Zustand zu erreichen.

Fortran ist in diesem Fall ein viel besseres und konsistenteres Beispiel.
Die Operation transpose ist nur für Matrizen in Fortran definiert.
Das Erstellen einer 1xn-Matrix aus einem echten 1-d-Vektor ist einfach keine Transponierung.
Für matmul siehe den Auszug aus dem in # 18056 zitierten Metcalf-Buch.

Ich denke, dass die meisten der ursprünglichen Punkte von @alanedelman richtig waren.

Hier ist also ein Vorschlag, der einfach einige bestehende Probleme heilen würde:
unter Berücksichtigung des aktuellen Zustands so weit wie möglich:

  • Behalten Sie v' wie beim Erstellen einer 1xn-Matrix aus einem echten 1-D-Vektor v
  • rowmx und colmx Funktionen wären besser, aber v' ist zu weit verbreitet, um sie zu ändern
  • Wir haben bereits die Funktion vec zum Erstellen eines echten 1-D-Vektors
  • Obwohl die Umkehrung von v' nicht v'' sondern vec(v') , können wir damit leben
  • Das a*b -Produkt sollte immer den letzten Index von a und den ersten Index von b kontrahieren
  • Der Operator * sollte weder für innere noch für äußere Produkte verwendet werden
  • Für innere Produkte haben wir bereits die Funktion dot
  • Für äußere Produkte sollte die Verwendung von * entfallen (_i.e._ die Syntax v*v' ).
  • Für äußere Produkte sollte eine neue Funktion verwendet werden
  • Ein entsprechender Infix-Operator könnte die Syntax leicht halten

Es wäre unglücklich, [prägnante mathematische Syntax für] äußere Produkte zu verlieren. Ich würde vec * mat lieber persönlich aufgeben.

Kann das vorhandene PernutedDimsArray bereits den Fall behandeln, in dem Eltern und Ansicht unterschiedliche Anzahlen von Dimensionen haben? Wenn ja, ist dies möglicherweise bereits als nicht konjugierter Transponierungs-Wrapper-Typ verwendbar, selbst für Vektoreltern.

Ich habe PermutedDimsArray in der Dokumentation nicht gefunden.

Aber ich habe das Gefühl, dass anstatt noch mehr Datentypen zu erfinden,
Nur die drei Arten von Array-Produkten sollten klar voneinander getrennt sein.
Innere Produkte sind bereits getrennt.
Wir müssen nur normale und äußere Produkte trennen.
Äußere Produkte würden nicht verloren gehen, nur ihre Syntax würde sich ändern.
Bitte betrachten Sie den Fall v*mx nur als Symptom für das tiefere Problem.

Abgesehen von dem "Problem mit fehlenden Methoden" wäre es kein Typ, über den Sie sich Sorgen machen müssten, sondern ein Implementierungsdetail, .' dem ' eine konjugierte Version davon). Ansonsten denke ich nicht, dass wir beide vec*mat und vec*vec' haben könnten, wenn wir denselben * Operator verwenden. Wenn ich vec*mat in einer Zeitung sehen würde, würde es für mich falsch aussehen, aber ich sehe vec*vec' ziemlich häufig.

Wenn ich jedoch mehr darüber nachdenke, glaube ich, dass PermutedDimsArray seine Elemente nicht rekursiv für Arrays von Arrays transponiert, sodass es als Wrapper-Typ, der hier verwendet werden soll, nicht ganz geeignet ist.

Die andere Alternative besteht darin, die Vektor-Transponierung insgesamt nicht zuzulassen. Ich denke, die Diskussion hier war bereits zu gründlich und wir warten nur auf eine umfassende Implementierung einer oder beider Optionen, um zu bewerten, wie die Auswirkungen aussehen werden.

Ich weiß es zu schätzen, dass Sie zu einer Diskussion bereit waren, während dieser Thread fast zu Ende ist.

Während v*mx für Sie seltsam aussehen mag, wird es im kristallografischen Code häufig verwendet.
Es wird auch von Fortrans matmul gut gehandhabt. (Siehe # 18056)

Zurück zum Produkt u*v' .
Wenn u und v beide nx1-Matrizen sind, ist dies ein normales Matrixprodukt.
das ergibt das gleiche Ergebnis wie das äußere Produkt.
Hierbei wird jedoch nur das Matrixprodukt verwendet, um das äußere Produkt zu emulieren.
Dies ist nahtlos in Matlabs Welt, in der alles eine Matrix ist.

In Julia haben wir echte 1-D-Vektoren, die näher an Fortrans Welt liegen.
In Julia ist der transponierte Vektor v' bereits ein Problem,
Fortrans Wahl ist es, es zu verbieten, wie es bereits von anderen für Julia vorgeschlagen wurde.

Fortran hat keine intrinsische Funktion für äußere Produkte.
Julia wandelt v' leicht in eine 1xn-Matrix um.
und führt die * -Operation an einem 1-d-Vektor und einer 1xn-Matrix aus.
Aufgrund des Mehrfachversands ist dies möglich.
aber hier ist * sicherlich kein Matrixprodukt mehr.

Der Punkt, an dem Fortran und Julia sich ähnlich verhalten
ist, dass beide eine separate dot -Funktion für innere Produkte verwenden.
Es gab eine Diskussion, um dot auch in den Operator * zu bündeln.
aber zum Glück ist es nicht passiert.

Wir müssen also mit den drei Produkten der linearen Algebra umgehen:
normales Matrixprodukt, inneres Produkt und äußeres Produkt.
Derzeit ist der Operator * eine kombinierte Syntax für normale und äußere Produkte.
Alles, was ich vorgeschlagen habe, ist, diese zu trennen und einen konsistenteren Zustand zu erreichen.

(Oh, ich habe das Kreuzprodukt weggelassen, aber es ist bereits gut getrennt)

Ich denke nicht, dass inneres Produkt, äußere Produkte und Matrixmultiplikation eine mathematisch fundierte Methode zum Teilen / Klassifizieren von Produkten sind. Das Folgende ist sicherlich eine Wiederholung von Dingen, die oben von verschiedenen Personen erwähnt wurden, aber angesichts der Länge dieses Themas hoffe ich, dass es in Ordnung ist, ab und zu eine Zusammenfassung beizufügen. Dies ist meine persönliche Zusammenfassung und ich bin sicherlich kein Experte. Korrigieren Sie mich also, wo ich falsch liege.

In einer abstrakten linearen Algebra-Einstellung sind die Hauptakteure Vektoren v (Leben in einem Vektorraum V ), lineare Karten (Einwirken auf einen Vektorraum V und Zuordnung zu a möglicherweise unterschiedlicher Raum W ) und Zusammensetzung dieser Karten, linearen Formen oder Covektoren (Leben in einem doppelten Raum V* und Zuordnung von V zu Skalaren), innere Produkte (von V × V zu Skalaren), Tensorprodukte zwischen Vektoren (dh kron ). Äußeres Produkt Ich stelle mir ein Tensorprodukt zwischen einem Vektor und einem Covektor vor.

Von besonderem Interesse für dieses Problem ist der Isomorphismus zwischen Vektoren und linearen Formen, wenn ein inneres Produkt definiert ist, dh für jedes f Mapping von Vektoren v auf Skalare existiert ein w so dass f(v) = dot(w,v) für jedes v . Covektoren können jedoch ohne Bezug auf innere Produkte existieren (z. B. den Gradienten einer mehrdimensionalen Funktion).

Um all diese Objekte auf einem Computer darzustellen, wählen Sie normalerweise eine Basis und können dann die meisten dieser Dinge mithilfe der Matrixalgebra darstellen. Das heißt, Sie benötigen Matrixmultiplikation und -transposition nur, wenn Sie bereit sind, mit nachfolgenden Singleton-Dimensionen (Vektoren als nx1-Matrizen, Skalare als 1x1-Matrizen usw.) flexibel zu sein. Dies war Matlabs Einstellung sowie die Art und Weise, wie viele Bücher und Papiere geschrieben werden, insbesondere in der numerischen linearen Algebra.

In diesem Fall entspricht der oben erwähnte Isomorphismus von Vektoren zu Covektoren (implizit unter Annahme des euklidischen Innenprodukts) der Transponierung (im komplexen Fall hermitisches Konjugat) der Matrixdarstellung eines Vektors. Im abstrakten Sinne gibt es jedoch keine Vorstellung von der Transponierung eines Vektors, weshalb er wahrscheinlich nicht in Fortran definiert ist, wie von @GaborOszlanyi angegeben . Es wird nur die Transponierung einer linearen Karte definiert (dies erfordert kein inneres Produkt und ihre Matrixdarstellung entspricht auch im komplexen Fall der transponierten Matrix) sowie der Zusatz einer linearen Karte (dies erfordert ein inneres Produkt). .

Aufgrund der Bedeutung der Typstabilität im Julia-Code funktioniert Matlabs Ansatz "nur Matrizen" (mit flexiblen nachfolgenden Singleton-Dimensionen) nicht gut. Wir wollen aber auch nicht zur vollständig abstrakten Umgebung übergehen und in der typischen Umgebung weiterarbeiten (euklidische innere Produkte, triviale Abbildung von Vektoren auf Covektoren, ...). Da wir am Ende Code schreiben und ASCII-Zeichen verwenden möchten, müssen wir so viel wie möglich aus den Symbolen * , ' und .' . Glücklicherweise ist hier der Mehrfachversand nützlich und führt zu den verschiedenen oben genannten Vorschlägen. Ich habe eine Tabelle darüber erstellt, dh wie abstrakte lineare Algebraoperationen in bestimmte Julia-Methoden (keine Funktionen) übersetzt werden.

Als letzte Anmerkung zu @GaborOszlanyi finde ich in v*A . Dies mag in Feldern Standard sein, in denen Vektoren standardmäßig als Zeilenmatrizen bezeichnet werden, aber ich persönlich halte dies für eine seltsame Wahl. Wenn die linearen Karten f und g als f(v) = v*A und g(v) = v*B g fungieren, bedeutet dies, dass g(f(v)) = (g ◦ f)(v) = v*A*B seit der Kompositionsreihenfolge ungerade ist ist vertauscht. Wenn überhaupt, könnte ich dies als unvollständiges inneres Produkt interpretieren, wie in der vorletzten Zeile der verknüpften Tabelle.

Ihre Zusammenfassung ist tief und überzeugend.
Danke, dass du es so gut erklärt hast.

Ich habe nur noch zwei Fragen:

  • Welche Veränderungen werden sich in Julia aufgrund dieser gründlichen Diskussion tatsächlich ergeben?
  • Warum hat Fortran v*mx in matmul implementiert?

Dieses Problem weist zwei Probleme auf:

A. Angewandte Mathematiker sind mit der Householder-Notation verbunden, die implizit zwei natürliche Isomorphismen verwendet:

  1. Ein Vektor der Länge N ist von einer Nx1-Spaltenmatrix nicht zu unterscheiden, da Nx1-Matrizen einen Vektorraum bilden. ("columnify", ▯)
  2. Eine 1x1-Matrix ist von einer Skalarzahl nicht zu unterscheiden, da 1x1-Matrizen mit allen algebraischen Eigenschaften von Skalaren definiert werden können. ("Skalarisieren", ■)

Das Problem ist, dass keiner dieser Isomorphismen in Julias Typen natürlich auszudrücken ist und beide Laufzeitprüfungen der Arrayform beinhalten. Die nachfolgenden Singleton-Dimensionsregeln von MATLAB können als Implementierung dieser beiden Isomorphismen angesehen werden.

B. Ein zweidimensionales Array kann rekursiv als Array von Arrays definiert werden. Eine Matrix ist jedoch eine Spalte von Spalten oder eine Spalte von Zeilen, jedoch niemals eine Zeile von Zeilen oder eine Spalte von Spalten. Die Tatsache, dass Matrizen nicht rekursiv definiert werden können, unterstreicht die unterschiedliche Tensorstruktur von Matrizen und n-dimensionalen Arrays. Richtigerweise sind mehrdimensionale Arrays eine sehr begrenzte Art von Tensor und verfügen nicht über die vollständige Maschinerie, die zur Implementierung des letzteren erforderlich ist. Da eine Kontraktion streng genommen eine Operation über eine Paarung eines Vektorraums mit seinem Dual ist, ist es eine Fehlbezeichnung, über die Kontraktion der Indizes mehrdimensionaler Arrays zu sprechen, die niemals das Konzept des dualen Vektorraums aufrufen. Die meisten Leute wollen nicht die volle Maschinerie, was erfordert, dass sie sich Gedanken über Co- / Contravariant- oder Up / Down-Indizes mit Zeilen- / Spaltencharakter machen. Stattdessen möchten die meisten Leute, dass Arrays einfache alte Container sind, die in allen Dimensionen völlig kontravariant sind, außer in zwei Dimensionen, wobei die meisten Benutzer sich zweidimensionale Arrays als die Algebra von Matrizen (die Down-Up-Tensoren sind) vorstellen möchten, und niemals die Down-Down-, Up-Up- oder Up-Down-Tensoren. Mit anderen Worten, zweidimensionale Arrays möchten speziell ummantelt werden.


Ich könnte mich immer noch davon überzeugen lassen, aber hier ist mein aktueller dreiteiliger Vorschlag:

a) Verbieten Sie das Transponieren von Vektoren vollständig, sodass Benutzer Vektoren explizit in Spaltenmatrizen konvertieren müssen, um Ausdrücke im Householder-Stil wie u'v , u*v' , u'*A*v und u'*A*v/u'v zu schreiben u und v wahre Vektoren sind, ist es nicht möglich, Unterausdrücke wie u' oder u'*A anzugeben, ohne ein spezielles TransposedVector einzuführen.

Eine Einschränkung von (a) wäre, dass alle Ausdrücke im Householder-Stil 1x1-Matrizen anstelle von echten Skalaren erzeugen (was immer noch eine enorme Verbesserung gegenüber u'v die einen 1-Vektor zurückgeben), also einen Ausdruck wie (u'*v)*w würde immer noch nicht funktionieren. In der Praxis glaube ich nicht, dass solche "Triple Product" -Ausdrücke häufig vorkommen.

b) Führen Sie eine alternative Notation für die analogen Operationen an Vektoren ein, wie z

  • u ⋅ v = scalarize(columnify(u)'*columnify(v)) für das innere (Punkt-) Produkt
  • u ⊗ v = columnify(u)*columnify(v)' für das äußere (Kronecker) Produkt
  • A(u, v) = scalarize(columnify(u)'*A*columnify(v)) , eine alte Notation für die bilineare Form
  • A(u) = A(u, u) für die quadratische Form

Die Ausdrücke in (b) unterscheiden sich von ihren Gegenstücken in (a) durch die automatische Skalierung der Ausdrücke für innere Produkt- und bilineare / quadratische Formen, wodurch die Bildung von 1x1-Matrizen vermieden wird.

c) Machen Sie 1x1-Matrizen konvertierbar in echte Skalare, so dass Code wie

M = Array(Int, 1, 1, 1)
a::Int = M

könnte klappen. Alles, was benötigt würde, ist die Laufzeitprüfung für die Arraygröße mit etwas wie:

function convert{T}(::Type{T}, A::Array{T,N})
    if length(A) == 1
        return A[1]
    else
        error()
    end
end

Dieser Vorschlag ist eine kleine Modifikation dessen, was Folkmar Bornemann vor etwa zwei Jahren vorgeschlagen hat. Als wir es ausprobierten, waren die Kosten für die Laufzeitprüfung nicht sehr hoch, und sie wurden nur bei typgesteuerter Zuweisung (was eigentlich eine getarnte Zuweisung von convert aufgerufen, nicht bei allgemeiner Zuweisung.

@jiahao , die Lesbarkeit dieses Beitrags ist durch die schwer zu rendernden Zeichen eingeschränkt. Sieht unter OS X nicht einmal richtig aus, da die Schriftarten normalerweise ziemlich vollständig Unicode enthalten.

@jiahao , ich könnte dem meisten / all dem sicherlich zustimmen, obwohl ich zwei Punkte herausfordern möchte:

ohne einen speziellen TransposedVector -Typ einzuführen, der als logische Konsequenz mehrdimensionale Arrays erfordert, um sich über Auf- / Ab-Indizes in ihrer Tensorstruktur Gedanken zu machen, was zu teuer erscheint.

Dies scheint mir nur eine logische Konsequenz zu sein, wenn Sie TransposedVector einem Subtyp der AbstractArray -Hierarchie machen möchten, von dem ich angenommen habe, dass er nicht der Plan ist (im Gegensatz zu einigen LazyTranspose -Typen, die wirklich sind ist nur eine andere Art von AbstractMatrix ).

u ⊗ v für das äußere (Kronecker) Produkt

Wenn das Ziel darin besteht, sich nicht auf implizite Isomorphismen zu verlassen und Matrixoperationen sauber von Vektoroperationen und abstrakteren Produkten zu trennen, scheitert dies meines Erachtens aus folgendem Grund (ich bin mir nicht sicher über die allgemeine Übereinstimmung mit den folgenden mathematischen Definitionen):

  • Das Kronecker-Produkt A ⊗ B ist eine Operation, die für Matrizen und nicht für Vektoren definiert ist.
  • Wenn also stattdessen u ⊗ v als Tensorprodukt zweier Vektoren gelesen wird, würde dies zu einem zweidimensionalen Tensor vom Typ Down Down führen. Der einzige Weg, um eine richtige "Matrix" zu erhalten, wäre, das Tensorprodukt eines Vektors mit einem Covektor zu nehmen. Da Sie jedoch vermeiden möchten, diese Objekte einzuführen, scheint es unmöglich zu sein, das Ergebnis dieser Operation zu erhalten, bevor die beiden beteiligten Vektoren kolumniert werden.
  • Ein dritter Name für u ⊗ v ist das äußere Produkt, das normalerweise schlampig definiert ist, und ich bin mir nicht sicher, ob es eine akzeptierte strenge Definition in Bezug auf Auf und Ab gibt. Einige Quellen geben an, dass es dem Tensorprodukt zweier Vektoren entspricht, daher der vorherige Punkt. Wenn Sie stattdessen eine Definition akzeptieren, bei der "äußeres Produkt" auch bedeutet, den zweiten Vektor implizit einem Covektor zuzuordnen, um einen Abwärts-Tensor zu erhalten, gibt es kein Problem.

Stattdessen möchten die meisten Leute, dass Arrays einfache alte Container sind, die in allen Dimensionen, außer in zwei Dimensionen, völlig kontravariant sind.

Haben wir über die nukleare Option nachgedacht? Wir beginnen die lineare Algebra von vorne, entfernen die Typealien für AbstractVector und AbstractMatrix vollständig und ersetzen sie durch:

abstract AbstractVector{T} <: AbstractArray{T,1}
abstract AbstractMatrix{T} <: AbstractArray{T,2}

# and we could introduce:
abstract AbstractCoVector{T} <: AbstractArray{T,1}

Ich verstehe, dass es viele Auswirkungen gibt, aber es könnte zu einer sauberen Trennung zwischen mehrdimensionalen Speicherarrays und linearer Algebra kommen. Wir müssen nicht die vollständige mehrdimensionale Tensoralgebra, duale Vektorräume usw. implementieren. Wir müssen nur die Bits implementieren, die viele Leute wollen: Vektoren, Covektoren und Matrizen. Wir erhalten innere Produkte, äußere Produkte, Covector-Matrix-Produkte, Matrix-Vektor-Produkte und Matrix-Matrix-Produkte. Die Transponierung ist für alle oben genannten Punkte definiert. Wir können Implementierungen von AbstractMatrix die wirklich ein 1D-Array von 1D-Arrays verwenden (natürlich nicht die Standardimplementierung). Wir müssen nicht die Householder-Notation verwenden (was meiner Meinung nach eine der Schwächen von MATLAB ist!), Aber wir können trotzdem alle Annehmlichkeiten der linearen Algebra nutzen.

Ich bin etwas nervös, dies vorzuschlagen, aber ich glaube, es wird Julia ermöglichen, das "richtige" Modell dessen nachzuahmen, was die meisten Menschen beispielsweise in der linearen Algebra auf Universitätsniveau im ersten Jahr lernen, ohne auf die Konventionen der Haushalte zurückgreifen zu müssen. Eine klare Unterscheidung kann es auch einfacher machen, Base.LinAlg in ein "Standardbibliothek" -Paket zu verschieben, was meiner Meinung nach ein langfristiges Ziel für Julia ist. Es passt auch gut zu der Idee, dass es eine neue listenähnliche 1D-veränderbare Sache geben wird, die mit den neuen Buffer Änderungen und der nativen Implementierung von Array einhergeht, also dieser generische "Listentyp" kann Vector für viele der Kernteile von Julia ersetzen und das Paket LinAlg ziemlich spät laden, während Array und "Liste" ziemlich früh definiert werden.

Es gibt viele Algorithmen, die bereits "heruntergekommen" sind und die als Fortran-Arrays und nicht als richtige lineare Algebra ausgedrückt werden. Julia muss in der Lage sein, diese Algorithmen zu implementieren, ohne dass Menschen eine lineare Algebra-Struktur auf mehrdimensionalen Arrays neu entdecken (oder erzwingen) müssen.

In meiner Arbeit ist wahrscheinlich eine richtige lineare Algebra am besten, die Tensoren und Co- / Kontravariantenindizes usw. auf Fortran-Arrays abbildet. Damit dies funktioniert - und um die Leute nicht zu verwirren - würde ich die Begriffe "Vektor" und "Matrix" und "Array" in Ruhe lassen, sie auf dem niedrigen Niveau halten und andere (schickere?) Begriffe für alles höhere Niveau verwenden . Diese höhere Ebene sollte auch Speicheroptionen abstrahieren, entweder über abstrakte Typen oder über Parametrisierung. Vielleicht ist ein Präfix LA der richtige Weg, um dies auszudrücken:

LA.Vector
LA.CoVector
LA.Tensor{... describing co/contravariance of indices ...}

Vektoren und Matrizen werden dann ausschließlich zur Speicherung verwendet. Auf der niedrigen Ebene wird die Transposition manuell verwaltet (wie in BLAS). auf der hohen Ebene wird es automatisch gehandhabt.

Dies kommt dem Vorschlag von @andyferris nahe, außer dass die Abwärtskompatibilität nicht beeinträchtigt wird und die Erwartungen von Matlab / Fortran / numpy convert nicht verletzt werden.

@eschnett Ich denke, früher in diesem Thread wurde entschieden, dass die multi-lineare Algebra eher für Pakete als für die Basis Julia übrig bleiben würde. Ich denke, viele dieser Ideen, wie Sie vorschlagen, könnten in einem neuen Paket konkretisiert werden, das Tensoren, Vektorräume, Co / Contra-Varianz usw. innerhalb des Typsystems behandelt, etwas anders als das Paket _TensorOperations.jl_, das Komfortfunktionen bietet zum Multiplizieren von Arrays, als wären sie Tensoren. Ich denke, es wäre schwierig zu entwickeln, aber möglicherweise eine lohnende Abstraktion!

Wenn Sie Matrix und Vector Ruhe lassen - vielleicht sind die aktuellen Definitionen perfekt, oder vielleicht können wir etwas Besseres für die Leute tun, die Matrizen multiplizieren und Vektoren mit Standardmathematik transponieren möchten Notation. Ich denke, es könnte neuen Benutzern eine kleine Lernkurve hinzufügen, obwohl ich gehofft hatte, wenn das System offensichtlich und eloquent war und eine Verbesserung für andere Sprachen, dann sollte es leicht zu lernen sein.

Beachten Sie, dass einem allgemeinen nichteuklidischen komplexen Vektorraum V 4 Leerzeichen zugeordnet sind: V , conj(V) , dual(V) und conj(dual(V)) . Ein allgemeiner Tensor hat also 4 Arten von Indizes (normalerweise als hoch oder runter, gesperrt oder nicht gesperrt). In einem komplexen euklidischen Raum (z. B. Quantenmechanik) dual(V) ≡ conj(V) und conj(dual(V)) = V . In einem realen (nichteuklidischen) Raum (z. B. allgemeine Relativitätstheorie) V ≡ conj(V) . In beiden letzteren Fällen werden nur Aufwärts- und Abwärtsindizes benötigt.

In einem realen euklidischen Raum (die meisten Anwendungen?) Sind alle gleichwertig, und Julias einfache Arrays reichen aus, um Tensoren darzustellen. Zumindest für reelle Zahlen ermöglichen die folgenden beiden Operationen die Konstruktion einer vollständigen und konsistenten Tensoralgebra.

  • Vertrag / inneres Produkt , das mit der folgenden Regel auf beliebige Arrays mit dem Rang N verallgemeinert werden kann: Kontrahieren Sie den letzten Index des ersten Arrays mit dem ersten Index des zweiten (oder Numpys dot Konvention, obwohl ich das weniger intuitiv finde), so dass A ∙ B ein Array von Rang M+N-2 zurückgibt, wenn A und B Rang M und Rang N .
  • Tensorprodukt : A ⊗ B gibt eine Reihe von Rang N+M

Spezialisiert auf Vektoren v , w und Matrizen A , B , ermöglicht dies das Schreiben aller folgenden Elemente:

  • Das innere Vektorprodukt v ∙ w -> gibt ausnahmsweise einen Skalar anstelle eines Arrays mit Rang 0 zurück
  • Matrixvektormultiplikation A ∙ v
  • Matrix Matrix Multiplikation A ∙ B
  • Tensor / Außenprodukt v ⊗ w
  • Covector (=== Vektor) Matrix Multiplikation v ∙ A

Während man vielleicht * für möchte, besteht die Gefahr darin, dass die obige Definition von nicht assoziativ ist: (A ∙ v) ∙ w ≠ A ∙ (v ∙ w) da letztere nicht einmal würde definiert sein.

Aber das ist nur möglich, wenn Sie bereit sind, komplexe Arrays zu ignorieren.

Was ganz allgemein die Umsetzung von Tensoren, begann ich (und verlassen) dies vor langer Zeit, bevor es Stapel reserviert Tupeln waren, erzeugten Funktionen und all die anderen Leckereien , die wahrscheinlich machen würde es heute mehr machbar.

Interessanter Ansatz, @Jutho. Scheint intuitiv genug. Ich vermute, dass diese Definitionen hinzugefügt werden könnten, unabhängig davon, was wir mit * und ' , und ich würde das unterstützen.

Aber das ist nur möglich, wenn Sie bereit sind, komplexe Arrays zu ignorieren.

Manuell eingefügtes conj() behebt das. Und ich glaube nicht, dass Julia komplexe Matrizen ignorieren will, oder?

Ich habe ein paar weitere Dinge bemerkt, AbstractMatrix darum ging,

matrix[:,i] -> AbstractVector{T}
matrix[i,:] -> AbstractCoVector{T}
array_2d[:,i] -> AbstractArray{T,1}
array_2d[i,:] -> AbstractArray{T,1}

Das ist ziemlich cool - wir können Spalten- und Zeilenvektoren durch Indizieren einer Matrix erhalten. Wenn es sich nur um einen 2D-Speichercontainer handelt, erhalten wir ein 1D-Speicherarray. Sollte alle Anwendungsfälle abdecken! Und es gehorcht immer noch der AbstractArray -Schnittstelle mit APL-Slicing-Regeln, da sowohl AbstractVector{T} <: AbstractArray{T,1} als auch AbstractCoVector{T} <: AbstractArray{T,1} .

Eine "interessante" Tatsache ist

array_3d[:,:,i] -> AbstractArray{T,2}
matrix(array_3d[:,:,i]) -> `AbstractMatrix {T}

Sie müssten es also manuell als Matrix umbrechen, wenn Sie eine Matrixmultiplikation mit dem Ergebnis durchführen möchten. Ist das ein Plus oder ein Minus, ich weiß es nicht? Dies scheint jedoch eine mögliche Komplikation zu sein und berührt das, was @eschnett erwähnt hat, um es Benutzern aus anderen Sprachen zu erleichtern, die Speicher und lineare Algebra miteinander verbinden.

Dies mag eine dumme Frage sein, aber @jutho erwähnte zuvor, dass das Schreiben von Code durch ASCII eingeschränkt wurde. Warum in aller Welt beschränken wir uns immer noch auf einen 7-Bit-Satz von Zeichen, der 1963 entwickelt und zuletzt 1986 (vor 30 Jahren) aktualisiert wurde? Dies war eine Ära, in der die berühmten 640 KB 1981 der maximal auf einem PC verfügbare RAM waren. Auf dem heutigen Computermarkt haben wir heute üblicherweise 64-Bit-Prozessoren mit 32 GB RAM im Angebot (50.000-fache des vorherigen Maximums) und sind bei weitem nicht in der Nähe die theoretische Grenze für 64-Bit-Prozessoren. Warum beschränken wir uns immer noch auf einen Zeichensatz, der vor 40 Jahren entwickelt wurde?

Wir können Unicode verwenden. Denken Sie jedoch daran, dass die Größe der Tastatur in den letzten 40 Jahren leider nicht um einen ähnlichen Faktor gewachsen ist und auch nicht die Anzahl der Finger, die ein normaler Mensch hat (in den letzten über 10000 Jahren).

IMHO sollte ASCII zur Ruhe gesetzt werden. Eine spezielle mathematische Tastatur für die schnelle Programmierung mathematischer Symbole ist eigentlich eine gute Idee! Gibt es einen guten Grund, nicht mehr Symbole zu verwenden, die mit UTF geliefert werden? Können Sie die Schmerzen rechtfertigen, wenn Sie versuchen, alles, was Sie können, aus ASCII herauszuholen?

@ esd100 : Bitte verwenden Sie dieses GitHub-Problem nicht für solch hochspekulative Diskussionen.

@ Johnmyleswhite. Ich bin mir nicht sicher, was du mit "spekulativ" meinst. Sind Sie sicher, dass Sie dieses Wort verwenden wollten? Ich dachte, die Verwendung von ASCII im Vergleich zu etwas anderem sei für die Diskussion relevant, da Sprache, Operatoren und Funktionalität anscheinend an die verwendeten Symbole gebunden sind. Ich bin keineswegs ein Experte, aber es scheint ein Problem zu sein, die angesprochene Funktionalität zu diskutieren oder zumindest klar zu machen.

Ich meine, gibt es einen Grund, warum wir die Sprache nicht so definieren können, wie wir es wollen (mit Blick auf die grundlegenden Ziele: Benutzerfreundlichkeit, Geschwindigkeit)? Würde die Verwendung spezieller Symbole / Wörter die Sprache nicht reicher und schöner machen?

@ esd100 Bitte beachten Sie, dass zB der jüngste Kommentar von @yuyichao zustimmt, dass wir Unicode verwenden können. Es gibt andere Vorschläge zur Einführung von mehr Unicode-Operatoren (z. B. das Erstellungssymbol zum Zusammenstellen von Funktionen, Nr. 17184). Ich glaube nicht, dass die Leute mit Ihnen nicht einverstanden sind (obwohl wir praktische Überlegungen berücksichtigen müssen), aber wenn Sie spezifische, themenbezogene Vorschläge haben, lassen Sie es uns bitte wissen.

Ich bin etwas verwirrt über dieses neue Verhalten in v0.5 das meiner Meinung nach aus dieser Diskussion hervorgegangen ist:

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.

Wie mache ich aus einem Listenverständnis einen (nicht numerischen) Zeilenvektor? Es muss ein Zeilenvektor sein. (Wäre dies besser als separates Thema oder woanders besprochen? War nicht sicher, wo ich posten soll ...)

Sie können die Umformung verwenden: reshape(v, 1, length(v)) . Vielleicht sollte dies auch in der Abwertungswarnung erwähnt werden? Ich denke, die Idee ist, dass Transponieren eine mathematische Operation ist und daher nur für mathematische Vektoren / Matrizen definiert werden sollte.

Es wird in der Depwarn erwähnt: permutedims(x, [2, 1]) .

permutedims funktioniert nicht für Vektoren. Siehe diese brandneue Ausgabe: # 18320

Ich denke, die Idee ist, dass Transponieren eine mathematische Operation ist und daher nur für mathematische Vektoren / Matrizen definiert werden sollte.

Das macht für mich keinen Sinn. Steht das zur Diskussion? Ich würde es wirklich vorziehen, zu transponieren, um an nicht numerischen Arrays zu arbeiten, es sei denn, es gibt eine wirklich gute Rechtfertigung.

Ich denke, die Idee ist, dass Transponieren eine mathematische Operation ist und daher nur für mathematische Vektoren / Matrizen definiert werden sollte.

Das macht für mich keinen Sinn. Steht das zur Diskussion? Ich würde es wirklich vorziehen, zu transponieren, um an nicht numerischen Arrays zu arbeiten, es sei denn, es gibt eine wirklich gute Rechtfertigung.

Ich denke, es ist relativ komplex, das zu befriedigen, was Sie wollen, und für die Mathematik Sinn zu machen. In einem mathematischen Sinne wird die Transponierung häufig durch Vertauschen von Vektorräumen und ihren Doppelräumen eines Vektors oder einer Matrix definiert (nun, es gibt auch einige Komplikationen bei der Transponierung gegenüber der konjugierten Transponierung). Für eine Matrix M aus regulären Zahlen haben wir die Transponierung als permutedims(M, (2,1)) , aber im Allgemeinen können Sie zB eine "Block" -Matrix in Bezug auf Untermatrizen wie diese definieren:

M = [A B;
     C D]

Dabei sind A usw. selbst Matrizen. Mein Verständnis ist, dass Julia gerne hat

M' = [A' C';
      B' D']

Das ist, was Sie mit Stift-Papier-Mathematik tun würden und ist daher "wünschenswerte Syntax".

Dies bedeutet, dass die Elemente einer Matrix ' und .' akzeptieren müssen. Für Zahlen werden diese als komplexe Konjugation bzw. No-Ops definiert. IMHO Ich denke, dies ist ein Wortspiel der Bequemlichkeit, aber es funktioniert und vor allem wird es mit einfachen Regeln implementiert ("transponieren ist rekursiv" - im Gegensatz zu BlockMatrix Klassen und so weiter). Dieses Wortspiel bei der Transponierung wurde jedoch in 0,5 aus Nicht-Zahlentypen entfernt, da es nicht viel Sinn macht. Was ist die Transponierung eines Symbol ?

Wenn Sie _Daten_ und keine Zahlen haben, ist die Verwendung von permutedims in ihrer Bedeutung und ihrem Verhalten perfekt definiert: Sie ist nicht rekursiv. Wenn Sie ein mathematisches Wortspiel wie .' verwenden, sparen Sie möglicherweise ein paar Zeichen beim Tippen - aber es ist für jemanden anderen (der mit Mathematik oder MATLAB möglicherweise nicht sehr vertraut ist) viel sinnvoller, Ihren Code zu lesen, wenn Sie ihn verwenden reshape und permutedims nach Bedarf. Für mich ist dies der wichtigste Punkt.

IMHO geht es in dieser sehr langen Ausgabe darum, eine mathematisch konsistente Schnittstelle für die lineare Algebra zu erstellen, und es scheint mir natürlich, dass das Ergebnis weniger mathematische Wortspiele für Datenfelder sein wird.

Danke für die nachdenkliche Antwort. Ich bin immer noch ziemlich anderer Meinung

Dieses Wortspiel bei der Transponierung wurde jedoch in 0,5 aus Nicht-Zahlentypen entfernt, da es nicht viel Sinn macht. Was ist die Transponierung eines Symbols?

Ich bin mir nicht sicher, ob die Definition der Transponierung eines Symbol als No-Op weniger sinnvoll ist als die Definition der Transponierung für eine reelle Zahl. Ich verstehe, dass das "Wortspiel" praktisch ist, aber es scheint, als ob das "Richtige" darin besteht, die Transponierung nur für Vektoren und Matrizen definiert zu haben und transpose(A::Matrix{Complex}) conj als Teil von anzuwenden seine Ausführung.

Wenn Sie ein mathematisches Wortspiel wie .' verwenden, sparen Sie möglicherweise ein paar Zeichen beim Tippen. Für andere Personen (die mit Mathematik oder MATLAB möglicherweise nicht besonders vertraut sind) ist es jedoch viel sinnvoller, Ihren Code zu lesen, wenn Sie ihn verwenden nach Bedarf umformen und permutieren. Für mich ist dies der wichtigste Punkt.

Ich bin damit einverstanden, dass .' mit Bedacht verwendet werden sollte und dass ein expliziterer Aufruf von transpose oft besser ist. Ich denke, reshape und permutedims können einen nicht trivialen kognitiven Aufwand erfordern, um überhaupt zu lesen und zu codieren. Seien Sie ehrlich, was schneller zu analysieren ist:

transpose([ f(x) for x = 1:length(A) ])
reshape([ f(x) for x = 1:length(A) ], 1, length(A))

Selbst in einfachen Fällen wie diesen müssen Sie vom Anfang der Zeile (um reshape zu lesen) bis zum Ende (um length(A) zu lesen) springen, um zu verstehen, was los ist. (Sie arbeiten sich dann wahrscheinlich zurück in die Mitte, um zu verstehen, warum length(A) ist.)

Für mich ist das größere Problem, dass wenn ich eine neue Julia bin (was bedeutet, dass ich wahrscheinlich schon einmal Numpy und MATLAB verwendet habe) und ich sehe, dass dies funktioniert:

[ x for x = 1:10 ]'

Ich gehe natürlich davon aus, dass das Gleiche für Arrays von Zeichenfolgen, Symbolen usw. funktioniert. Ich werde keine langen Online-Diskussionen durchlesen, um die Semantik von .' herauszufinden - ich bin Ich werde einige Dinge ausprobieren und durch vergangene Erfahrungen verallgemeinern / schließen. Vielleicht hilft hier eine bessere Fehlermeldung.

Insgesamt sehe ich nicht, wie die Beibehaltung der No-Op-Transponierung auf Symbol und anderen nicht numerischen Eingaben den hier vorgeschlagenen netten mathematischen Rahmen beeinträchtigt (ein würdiges Ziel!). Aber das No-Op zu behalten scheint harmlos.

Aber das No-Op zu behalten scheint harmlos.

Sie können für Any nichts wirklich Sinnvolles definieren, da alles ein Subtyp von Any . Die Definition transpose(x)=x ist für jede Art von Matrix einfach falsch. Um einige der stillen Fehler zu vermeiden, mussten wir diese Definitionen hinzufügen. Es ist also ein Kompromiss zwischen der Bequemlichkeit, die relativ seltsame Syntax ' für eine völlig nicht mathematische Operation zuzulassen und stille Fehler zu vermeiden.

Ich bin mir nicht sicher, ob die Definition der Transponierung eines Symbols als No-Op weniger sinnvoll ist als die Definition der Transponierung für eine reelle Zahl. Ich verstehe, dass das "Wortspiel" praktisch ist, aber es scheint, als ob das "Richtige" darin besteht, die Transponierung nur für Vektoren und Matrizen definiert zu haben und transpose(A::Matrix{Complex}) conj als Teil von anzuwenden seine Ausführung.

Ich bin nicht ganz anderer Meinung, aber wir müssten eine Art von BlockMatrix implementieren oder spezielle Methoden für Matrix{M<:Matrix} . Ich bin mir nicht sicher, ob entweder eine beliebte Idee ist oder nicht? (Dies ist eine ernste Frage für diejenigen, die verfolgt haben, da dies einige dieser verwandten Probleme vereinfachen könnte.)

Seien Sie ehrlich, was schneller zu analysieren ist:

transpose([ f(x) for x = 1:length(A) ])
reshape([ f(x) for x = 1:length(A) ], 1, length(A))

Die zweite, weil ich mich nicht auf Julias aktuelle Transposition von Vektoren beziehe / mag (klar, ich nehme Vektortransponierungen zu ernst) :) Wenn ich die zweite machen müsste, würde ich wörtlich rowvec = reshape(colvec, (1, n)) schreiben, oder wahrscheinlich nur [f(x) for _ = 1:1, x = 1:n] , um das Verständnis zu zwingen, zunächst die richtige Form zu erhalten, oder wenn Sie .' wirklich mögen, dann funktionieren derzeit auch map(f, (1:n).') und f.((1:n).') .

Es ist ein Kompromiss zwischen der Bequemlichkeit, die relativ seltsame Syntax für eine völlig nicht mathematische Operation zuzulassen und stille Fehler zu vermeiden

Wenn dies zu stillen Fehlern und anderen Problemen führte, werde ich dieses Argument wahrscheinlich verlieren. (Ich verstehe nicht, warum es Fehler verursachen würde - aber ich glaube Ihnen.) Auf der anderen Seite ....

Wir müssten eine Art BlockMatrix implementieren oder spezielle Methoden für Matrix {M <: Matrix} definieren.

Ich könnte mich irren, aber ich denke, Sie müssten nur die zweite machen, was mir als vernünftige Option erscheint. Aber das fängt an, über meine Gehaltsstufe zu kommen.

[f (x) für _ = 1: 1, x = 1: n]

Ich habe das vergessen! Dies ist wahrscheinlich, was ich am Ende tun werde. Insgesamt bin ich immer noch nicht einverstanden mit Ihrem Geschmack für lesbaren Code, aber für jeden seinen eigenen! ¯\_(ツ)_/¯

klar, ich nehme Vektortransponierungen zu ernst

Ja. Ich glaube schon. 😉

Dies (https://github.com/JuliaLang/julia/issues/16790) würde auch die Verwendung von reshape anstelle von transpose für mich etwas schmackhafter machen.

Ich könnte mich irren, aber ich denke, Sie müssten nur die zweite machen (bearbeiten: spezielle Transponierungsmethoden für Matrix{M<:Matrix} ), was mir als vernünftige Option erscheint.

Leider kehren wir jetzt noch einmal zur Unterscheidung zwischen Daten und linearer Algebra zurück. Sie möchten, dass lineare Algebra-Blockmatrizen eine rekursive Transponierung haben, aber generische 2D-Datenarrays von 2D-Datenarrays keine rekursive Transponierung haben ... aber während Matrix{T} und Array{T,2} dasselbe sind, können wir das nicht TU das.

Dies (# 16790) würde es mir auch etwas schmackhafter machen, die Umformung anstelle der Transponierung zu verwenden.

Wahr!!

Sie möchten, dass lineare Algebra-Blockmatrizen eine rekursive Transponierung haben, aber generische 2D-Datenarrays von 2D-Datenarrays keine rekursive Transponierung haben

Dies scheint nicht etwas zu sein, das ich offensichtlich möchte ... Warum nicht einfach zwei verschiedene Funktionen haben, um mit diesen verschiedenen Arten von Transponierungen umzugehen? Ich glaube, ich habe das Memo verpasst, da es eine sehr strenge Unterscheidung zwischen linearen Algebra-Objekten und Datenobjekten gibt.

Das Lesen dieses ganzen Threads scheint eine entmutigende Aufgabe zu sein, aber vielleicht sollte ich das tun, bevor ich weiter kommentiere. Ich möchte kein uninformiertes Rauschen hinzufügen.

Ich glaube, ich habe das Memo verpasst, da es eine sehr strenge Unterscheidung zwischen linearen Algebra-Objekten und Datenobjekten gibt.

Es gibt keine strikte Unterscheidung zwischen linearen Algebra-Objekten und Datenobjekten.
Weder in der aktuellen Implementierung noch im idealen Einsatz.

Wenn dies der Fall wäre, würde das Ändern der Größe (mit push! ) oder sogar der Werte von linearen Algebra-Objekten nicht unterstützt (und solche Benutzer würden StaticArrays.jl usw. verwenden), und nur broadcast auf linearen Algebra-Objekten unterstützt werden.
Datenobjekte wären modifizierbar, erweiterbar und würden map (und reduce und filter ) unterstützen, aber nicht broadcast .

Aber wir leben nicht in einer Welt, in der Menschen entweder binär an Datenobjekte denken oder an Objekte der linearen Algebra.
Also dieser 2,5 Jahre, 340 Kommentarthread.


Bezüglich der no-op Transponierung.
Wir könnten ganz oben in der Typhierarchie einen abstrakten Typ Scalar und Nonscalar hinzufügen.
Scalars alle fallen auf eine No-Op-Transponierung zurück.
Nonscalars haben keinen Rückfall (aber im Moment auf eine Abwertungswarnung zurückgreifen)

Zahlen, Zeichen, Zeichenfolgen und vielleicht sogar Tupel wären Scalar und hätten eine No-Op-Transponierung definiert. Einige Skalare, z. B. komplexe Zahlen, würden diese Definition der Transponierung überschreiben.

Arrays (Matrix, Vektoren und andere) wären Subtypen von Nonscalar und hätten eine rekursive Transponierung.

Ich bin mir nicht sicher, ob mir das gefällt oder nicht.

Die jüngste Flut von Beiträgen wiederholt Diskussionen über _matrix transponierte_ und "skalare" Typen in # 18320 # 13171 # 13157 # 8974.

Ich möchte wirklich die Vorstellung zerstreuen, dass der No-Op-Fallback transpose(x)=x harmlos ist. Meiner Meinung nach sollte ein Fallback immer noch das richtige Ergebnis liefern, nur langsamer als ein optimierter Algorithmus. Es ist gefährlich, wenn die Fallback-Methode stillschweigend die falsche Antwort berechnet, da dies bedeutet, dass der Fallback nicht die richtige Semantik hat: transpose(x) bedeutet je nach Art von x verschiedene Dinge.

Der No-Op-Transponierungs-Fallback ist nicht nur für Matrizen von Matrizen falsch, sondern auch für alle matrixartigen Objekte, die keine Untertypen von AbstractMatrix (was mit # 987 bedeutet, dass sie explizit Einträge gespeichert haben, _not_ dass sie die Algebra der Matrizen haben). Hier ist ein Beispiel für ein Aushängeschild (von dem wir einige haben):

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

Dieses Beispiel zeigt, dass nur weil etwas ein Subtyp von Any dies nicht bedeutet, dass Sie davon ausgehen können, dass es ein Skalar ist und eine No-Op-Transponierung hat. Es zeigt auch, warum das übergeordnete Problem im OP so schwer zu lösen ist. Für ein matrixartiges Nicht-Array-Objekt Q hat Q' keine wirkliche Bedeutung als Array-Operation, aber eine eindeutige algebraische Bedeutung: Ausdrücke wie Q'A sind perfekt definiert. Andere Leute, die mit Arrays und nicht mit linearer Algebra arbeiten, wollen einfach nicht rekursive Permutationen und interessieren sich nicht für matrixartige Nicht-Arrays. Es bleibt jedoch die Tatsache, dass Sie nicht für alle Typen eine konsistente Schreibweise sowohl der algebraischen als auch der Achsenwechselsemantik haben können.

Vielleicht bin ich dicht, aber ich hätte gedacht, der Vergleich wäre folgender:

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

Das Überschreiben von transpose auf diese Weise scheint transpose([ :x _=1:4 ]) - dh was ich geschrieben habe. Ich hätte gedacht, dass der Fallback niemals aufgerufen werden würde, solange Sie transpose / ctranspose für alles, was es benötigt (z. B. QRCompactWYQ ), korrekt implementieren (da es sich um einen spezifischeren Aufruf handelt) kann gemacht werden).

Ihr Code ruft nicht die ctranspose Ihnen geschriebene @which überprüfen). Es ruft eine andere Fallback-Methode in Julia v0.5 auf (die in v0.4 nicht existiert), die im Wesentlichen ctranspose(full(Q)) ausführt. Dieser andere Fallback ist korrekt, besiegt aber genau den Grund, warum wir diesen ausgefallenen Q-Typ haben (damit das Multiplizieren mit ihm genau durchgeführt werden kann). Mein Kommentar, dass Fallbacks korrekt sein sollten, bleibt bestehen.

Ja, solange Sie die Umsetzung für alles richtig implementieren
braucht es, der Fallback schadet natürlich nicht. Der Schaden ist, dass Sie nicht
Wenn Sie dies vergessen, erhalten Sie einen Fehler ohne Methode, aber im Stillen das Falsche
Ergebnis.

Danke @toivoh, dass es für mich

Aber wenn Sie eine Fallback-Funktion transpose(X::AbstractMatrix) und transpose(X::AbstractVector) Sie vermutlich immer das richtige Ergebnis (auch wenn es langsam war ... zum Beispiel full aufrufen) nein? Und Sie könnten immer eine speziellere Funktion schreiben, um es besser / schneller zu machen. Dann sollte transpose(::Any) niemals andere stille Fehler als die zuvor erwähnten "Wortspiele" verursachen (dh beim Umgang mit Complex Zahlen ... vielleicht andere skalare Anwendungsfälle, von denen ich nichts weiß?).

Aus historischen Gründen QRCompactWYQ <: AbstractMatrix , aber gemäß # 987 # 10064 ist diese Subtypisierungsbeziehung nicht korrekt und sollte entfernt werden.

Ein weiteres Beispiel für ein matrixartiges Nicht-Array ist IterativeSolvers.AbstractMatrixFcn , was kein Subtyp von AbstractMatrix . Für diesen Typ würden die Fallbacks, auf die Sie verweisen, niemals versendet, was wiederum mein Hauptpunkt war.

Wir sollten diese Diskussion unter https://github.com/JuliaLang/julia/issues/13171 fortsetzen. Das Problem betrifft eigentlich hauptsächlich Vektoren mit zahlenähnlichen Elementen.

Jemand von "Team Linear Algebra" muss sich dazu verpflichten, etwas dagegen für 0,6 zu tun, sonst wird es wieder stoßen.

Was ist der eigentliche Plan zum Neustart?

Meine Denkweise bringt mich zu: transpose(v::AbstractVector) = TransposedVector(v) wo TransposedVector <: AbstractVector . Die einzige semantische Sache, die TransposedVector von AbstractVector ist das Verhalten unter * (und allen A_mul_B s, \ , / , ...). Dh es ist ein Dekorateur, der bestimmt, welche Indizes unter * (etc ...) kontrahieren sollen. Die Transposition wäre ein lineares Algebra-Konzept. Wenn Sie ein Array von "Daten" neu anordnen möchten, sollten reshape und permutedims empfohlen werden.

Die einzige semantische Sache, die TransposedVector von AbstractVector ist das Verhalten unter *

Also v'==v aber v'*v != v*v' ? Dies mag zwar sinnvoll sein, sieht aber möglicherweise auch verwirrend aus.

Also v'==v aber v'*v != v*v' ? Dies mag zwar sinnvoll sein, sieht aber möglicherweise auch verwirrend aus.

IMO ist dies unvermeidlich (obwohl möglicherweise unglücklich).

Das Dual eines Vektors ist auch ein Vektor. Die Transponierung ist auch immer noch eine eindimensionale Datenstruktur mit genau definierten Elementen, die in der Lage sein sollten, zu erhalten, zu mutieren, abzubilden, zu reduzieren usw.

Wenn wir die lineare Algebra nicht von Arrays trennen (z. B. Matrix{T} sowohl zu einem Subtyp als auch zu einem unveränderlichen Wrapper von Array{T,2} , wobei mehr (lineare Algebra-spezifische) Methoden definiert sind), bin ich mir dort nicht sicher ist eine große Auswahl, die sowohl mit den Eigenschaften des Arrays als auch der linearen Algebra übereinstimmt.

Die eine verwirrende Frage (für mich) ist, ob es wie ein Vektor oder über seine zweite Dimension sendet. Wenn die Größe (1, n) dann macht diese Änderung nicht wirklich viel, außer zu behaupten, dass sie wie eine Matrix ist, in der die erste Dimension bekanntermaßen die Länge 1 . Die Transponierung eines Vector{T} müsste ein Array{T,2} bleiben (dh Matrix ...), aber die Transponierung davon könnte wieder ein Vector sein ( zB könnten wir v'' === v ).

Ist das eine bessere Idee? Es wäre weniger brechend und würde die Semantik der linearen Algebra noch verbessern. BEARBEITEN: und es verhält sich anders als bei == wie @martinholters aufruft ).

Für mich klingt es nach dem vernünftigsten Ansatz, TransposedVector{T <: AbstractVector{Tv}} <: AbstractMatrix{Tv} *) mit size(::TransposedVector, 1)==1 aber transpose(::TransposedVector{T})::T , aber es gab so viele Debatten, dass es wahrscheinlich ein gutes Argument dagegen gibt?

*) Ich weiß, dass dies syntaktisch ungültig ist, aber ich hoffe, dass die beabsichtigte Semantik klar ist.

Ja, wenn ich mit Ideen im Code spiele, stimme ich Ihnen @martinholters zu.

Ich habe unter https://github.com/andyferris/TransposedVectors.jl angefangen. Dies alles kann mit nur einer geringen Menge an Typ-Piraterie und Methodenüberschreibungen von einem Paket außerhalb der Basis erreicht werden, und ich habe ein Paket erstellt, das mit Julia 0.5 kompatibel ist. Wenn es gut ausgeht, könnten wir es vielleicht auf Base portieren? (Oder lernen Sie einige Lektionen).

Eine große Frage ist, ob wir für eine komplexe Konjugation ohne einen CTransposedVector -Typ leben können oder ob dies jedoch behandelt wird.

Ich würde kein separates CTransposedVector , da dies zwei Konzepte (Konjugation und Umformung) in einem Objekt kombiniert. Es ist viel einfacher, diese beiden Konzepte als separate Einheiten zu implementieren. Mit MappedArrays.jl ist es beispielsweise so einfach wie

conjview(A) = mappedarray((conj,conj), A)

und Sie erhalten auch eine großartige Leistung.

Richtig, danke Tim. Ich habe über so etwas nachgedacht / gehofft - meine einzige Sorge war, wie Sie sicherstellen können, dass die beiden Wrapper immer in der "richtigen" Reihenfolge angezeigt werden, aber ich denke, was ich bisher implementiert habe, kann damit umgehen. Außerdem müssten wir BLAS für all diese Kombinationen unterstützen, was ich bisher vermieden habe, zu berühren.

Eine schöne Sache ist, dass eine separate Änderung, die standardmäßig conj auf conjview , unabhängig von dieser Änderung wäre (ich denke, sie könnten beide in unabhängigen Paketen gebastelt werden und sie würden korrekt zusammensetzen). Gibt es Appetit darauf, conj eine Ansicht zu machen? Warten wir darauf, dass Inline-Non-Isbits-Unveränderliche solche Ansichten frei machen? Oder müssen Sie nicht warten?

@andyferris : Ich denke, Ihr Ansatz ist gut - danke, dass Sie ihn vorangetrieben haben. Wenn die Implementierung gut funktioniert, können wir diesen Code einfach in Base verwenden. Eine Sache, die Sie beachten sollten, ist, dass wir möglicherweise auch TransposedMatrix für https://github.com/JuliaLang/julia/issues/5332 wünschen

Nachdem wir die Konvertierung fortgesetzt haben, möchte ich noch einmal erwähnen, dass wir versuchen sollten, einen transponierten Vektortyp zu vermeiden. Es wurde oben einige Male erwähnt, aber ich bezweifle, dass jemals wieder jemand alle Kommentare zu diesem Thema lesen kann. Die Einführung eines Transponiertyps für Vektoren verkompliziert die Dinge wirklich fast ohne Nutzen. Wenn Sie wirklich denken, dass ein Vektor eine Richtung hat, dann machen Sie ihn Matrix und die Dinge werden einfach funktionieren. Es macht wenig Sinn, lineare Algebra-Vektoren zu haben, die sich von Matrizen unterscheiden, und dann einen neuen Wrapper-Typ einzuführen, damit sich die Vektoren wie 1xn Matrizen verhalten.

Im Moment besteht die Asymmetrie darin, dass x' x in eine Matrix befördert, während A*x die x in eine Matrix befördert. Wenn lineare Algebraoperationen nur für 2D wären, sollte A*x fördern und x'x wäre eine 1x1 Matrix. Alternativ könnten wir Vektoren Vektoren sein lassen und daher auch A*x ein Vektor sein, aber dann sollte x' ein Noop oder ein Fehler sein. Die Einführung eines transponierten Vektors erfordert viele neue Methodendefinitionen und der einzige Vorteil scheint zu sein, dass x'x ein Skalar wird.

Ich denke, die Matrixtransponierung ist sehr unterschiedlich. Es wird uns ermöglichen, alle Ax_mul_Bx -Methoden loszuwerden, und das Verhalten ist nicht so umstritten wie das Verhalten von Vektortransponierungen.

Ich sehe nicht wirklich, wie die Einführung eines TransposedVector-Typs mehr Probleme verursacht als die Einführung eines TransposedMatrix-Typs.

Ja, der starke Konsens irgendwo in der Mitte dieses Opus war, dass die Vektortransponierung seltsam ist und dass sie, wenn sie implementiert wird, definitiv kein Subtyp von AbstractArray - ich würde sogar size nicht zulassen obigen Zusammenfassung sind einige der Herausforderungen aufgeführt, die mit dem Einfügen eines Covektors verbunden sind - nicht zuletzt die Notation (beachten Sie, dass ich Tims Ansatz empfehlen würde, conj über das Array abzubilden, anstatt es als Typ einzuschließen Parameter).

Es gab jedoch einen noch stärkeren Aufruf, dass die Vektortransponierung einfach ein Fehler sein sollte. Wenn das der Fall wäre, dann könnte es tatsächlich in einem Paket leben.

Vektortransponierung ein Fehler zu sein, scheint das Baby mit dem Badewasser herauszuwerfen. Nicht in der Lage zu sein, v' zu schreiben, um einen Vektor zu transponieren, wäre wirklich sehr, sehr ärgerlich. Ich verstehe nicht wirklich, was an einem TransposedVector-Typ so schlimm ist, der sich wie eine Zeilenmatrix verhält, abgesehen davon, wie er sich mit Vektoren und Matrizen multipliziert.

Dies hängt davon ab, wie viele Verhaltensweisen Sie mit einer Vektortransponierung behandeln möchten. Wenn Sie einfach v'' == v , dann ist es in Ordnung für typeof(v') <: AbstractMatrix . Aber wenn Sie die anderen Dinge wollen, über die wir in diesem Thread gesprochen haben, wie typeof(v'v) <: Scalar oder typeof(v'A) <: AbstractVector , dann muss es ein anderes, komplizierteres Tier sein.

Die Idee, TransposedVector als eine Art Vektor zu haben, scheint die Wurzel vieler Probleme zu sein. Es scheint viel weniger störend zu sein, wenn es eine Art Matrix wäre und dann size(v.') == (1,length(v)) so wie wir es jetzt tun. Der einzige Unterschied wäre, dass eine doppelte Transponierung Ihnen einen Vektor zurückgibt und v'v einen Skalar erzeugen würde. Wenn man eine Transponierte als Vektor behandeln möchte, kann man, da length(v') die richtige Antwort gibt und die lineare Indizierung wie erwartet funktioniert.

Ich stimme 110% mit @StefanKarpinski überein. Ich habe damit gespielt, es zu einer Art Vektor zu machen, aber es macht nicht allzu viel Sinn und ist aus den Gründen, die weiter oben in diesem Thread besprochen wurden, ziemlich kaputt.

Der Ansatz, die Größe (1, n) bedeutet, dass sich das Verhalten von Array genau so verhält, wie es jetzt ist. Die _only_-Operationen, die es von einem 1-mal-N Matrix sind Verhaltensweisen unter ' , .' , * , \ . und / .

Keiner dieser Operatoren ist "Array-ähnlich". Sie dienen lediglich der Implementierung der linearen Algebra zwischen Matrizen und Vektoren und stammen direkt von MATLAB ("Matrixlabor"). Diese letzte Verfeinerung besagt einfach, dass die size(tvec, 1) = 1 statisch für den Compiler _und_ sind, dass v'' v . (Ich denke, es ist ein bisschen wie ein StaticArray bei dem eine Dimension fest und die andere dynamisch dimensioniert ist).

Wenn Sie einfach v '' == v wollen, ist es für typeof (v ') <: AbstractMatrix in Ordnung.

Recht.

Aber wenn Sie die anderen Dinge wollen, über die wir in diesem Thread gesprochen haben, wie typeof (v'v) <: Scalar oder typeof (v'A) <: AbstractVector, dann muss es ein anderes, komplizierteres Tier sein.

Warum? Wir brechen keine seiner Array-ähnlichen Eigenschaften, daher kann es sich immer noch um ein Array handeln. Jeder Punktaufruf funktioniert wie zuvor, und andere vektorisierte Operationen werden entfernt. Ich denke, dass * auf das Wissen über die verschiedenen Formen von Vector und Matrix "spezialisiert" ist und dass dies daran liegt, dass wir versuchen, das Verhalten der geschriebenen linearen Algebra zu emulieren. v' * v zu einem Skalar zu machen, ist _sehr_ Standardmathematik und es ist meine Interpretation, dass der einzige Grund, warum dies nicht von Anfang an so war, darin besteht, dass es nicht trivial ist, es auf konsistente Weise zu implementieren (und die Inspiration, die Julia daraus zieht MATLAB), aber Stefan konnte das klarstellen. Das innere Produkt zu einem Skalar zu machen, ist die einzige bahnbrechende Änderung, von der hier die Rede ist (es gibt andere sehr kleine Dinge) - ich verstehe nicht, warum dies allein es ungeeignet machen würde, ein Array (es gibt viele Arten von Array , die keine gültigen ' , .' , * , \ und / definiert haben _at all_ , merklich Rang 3+)

Eines der Probleme, auf die ich letztes Jahr gestoßen bin, waren Unklarheiten; IIRC waren sie viel einfacher zu lösen, wenn die Vektortransponierung kein Array war.

Ja, ich mache mir ein bisschen Sorgen, wie tief das werden wird, bevor ich fertig bin ... :)

Generell wäre es wunderbar, wenn wir Base weniger monolithisch machen und es in Standardbibliotheken zerlegen würden. Ein in sich geschlossenes Paket oder Modul, das nur AbstractArray und Array würde diese Art der Entwicklung vereinfachen.

Was genau war das Problem mit @Juthos Vorschlag? Konnte * den Vektor links nicht automatisch (konjugieren) transponieren?

Wenn wir * die Matrixmultiplikation haben, ' die Matrix (konjugierte) Transposition (lässt die Vektoren unverändert) und zwei Operatoren promote , die einen nachgestellten Singleton und demote hinzufügen *: (n,m) -> (m,) -> (n,) , eine linke Anwendung *: (n,) -> (n,m) -> (m,) , ein skalares Produkt *: (n,) -> (m,) -> (,) und ein äußeres Produkt ×: (n,) -> (m,) -> (n,m) erstellen

(*)(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)'

Ich bin mir nicht sicher, ob ich jemals einen Vektor mit diesen Operatoren transponieren müsste. Vermisse ich etwas

Bearbeiten: Nicht genau @Juthos Vorschlag.

@Armavica , ich bin mir nicht ganz sicher, ob es genau mein Vorschlag war, diese Bedeutung * zuzuweisen, sondern einem neuen (Unicode-) Operator , der derzeit dot . Darüber hinaus glaube ich nicht, dass Sie den Ansatz von promote und demote typstabil implementieren können.

Der Purist in mir stimmt @andreasnoack zu, dass die Transponierung eines Vektors selbst vermieden werden sollte (ich persönlich bevorzuge es, jederzeit dot(v,w) über v'w oder v'*w schreiben), aber ich kann es auch schätzen die Flexibilität, es so arbeiten zu lassen, wie es von @andyferris vorgeschlagen wurde. Daher werde ich dieser Diskussion nichts weiter hinzufügen und jeden vernünftigen Versuch unterstützen, eine tatsächliche Implementierung zum Laufen zu bringen.

Richtig, @Jutho. Interessanterweise schließen sich die beiden nicht gegenseitig aus: Wenn standardmäßig keine Transponierung auf einem Vektor definiert ist, kann diese Funktionalität legitimerweise in einem separaten Paket, Modul oder einer "Standardbibliothek" (dh ohne "Typpiraterie") gespeichert werden.

(Ich persönlich bevorzuge es, jederzeit Punkt (v, w) gegenüber v'w oder v '* w zu schreiben.)

Jutho - andererseits wette ich

Natürlich würde ich Diracs Braket-Notation gerne als Standardformulierung der linearen Algebra in der gesamten Mathematik oder sogar in den Naturwissenschaften und im weiteren Sinne in Julia sehen, aber wahrscheinlich wird es nicht passieren.

Jetzt zwingst du mich abzuschweifen, was ich nicht mehr tun würde. Ich bin sicher damit einverstanden, <ψ | ψ> für das innere Produkt zu bevorzugen, aber das ist unabhängig davon, dass ich nicht an <ψ | denke als das hermitische Konjugat von | ψ>. Die hermitische Konjugation ist für Operatoren definiert. Der natürliche Isomorphismus zwischen Vektoren und ihren zugehörigen Doppelvektoren (Covektoren, lineare Funktionale, ...) ist als Riesz-Repräsentationssatz bekannt , obwohl der erstere natürlich der hermitianischen Konjugation entspricht, wenn Längenvektoren n als lineare Karten interpretiert werden von C nach C ^ n, dh als Matrizen der Größe (n,1) .

Natürlich würde ich Diracs Braket-Notation gerne als Standardformulierung der linearen Algebra in der gesamten Mathematik oder sogar in den Naturwissenschaften und im weiteren Sinne in Julia sehen, aber wahrscheinlich wird es nicht passieren.

Lol

Jetzt zwingst du mich abzuschweifen, was ich nicht mehr tun würde.

Entschuldigung ... ich hätte es wirklich nicht erwähnen sollen ... :)

Ich denke nicht an <ψ | als das hermitische Konjugat von | ψ>. Die hermitische Konjugation ist für Operatoren definiert.

Das hatte ich zwar nicht bedacht.

Der natürliche Isomorphismus zwischen Vektoren und ihren zugehörigen Doppelvektoren (Covektoren, lineare Funktionale, ...) ist als Riesz-Repräsentationssatz bekannt, obwohl ersterer natürlich der hermitianischen Konjugation entspricht, wenn Längen-n-Vektoren als lineare Abbildungen von C nach C ^ interpretiert werden n, dh als Matrizen der Größe (n, 1).

Ich versuche nicht abzuschweifen (aber ich bin schlecht darin), aber ich denke, wir bekommen ein bisschen von diesem letzten Stück, da wir AbstractVector mit einem 1D Array assoziieren. Mit anderen Worten bedeutet AbstractVector nicht "abstrakter Vektor" im mathematischen Sinne, sondern "die numerischen Elemente eines Vektors auf einer vordefinierten Basis". MATLAB kam leicht heraus, weil die Vektoren die Größe (n,1) .

Wie nennt man als Denkanstoß den (antilinearen) Operator, der alle Tensoren der Form |a>|b><c| annimmt und sie |c><a|<b| zuordnet? Ich habe dies immer zusammen mit der Vektor-Dual- und Standard-Operator-Konjugation als "Hermitianische Konjugation" gebündelt, aber vielleicht ist das zu blasiert.

Hatte ein Gespräch mit @alanedelman, das ein paar Dinge über meine Position auf # 4774 herauskristallisierte:

  • den Typ Covector oder RowVector einführen;
  • für lineare algebraische Operationen ( * , ' , .' , / , \ , vielleicht norm ? andere? ) dieser Typ wirkt als Covektor in der linearen Algebra;
  • Für Array-Operationen (alles andere) fungiert dieser Typ als zweidimensionales 1 × n-Array.
  • Insbesondere ist size(v') eines Covectors (1, length(v)) .

Dieser Ansatz erfordert die Klassifizierung aller generischen Funktionen in Base als linear algebraisch oder nicht. Insbesondere ist size eine Array-Funktion und macht im Bereich der linearen Algebra, die im Wesentlichen nur Vektoren (die keine "Form" haben, nur eine Kardinalität von Dimensionen haben), lineare Transformationen (welche) keinen Sinn kann durch zweidimensionale Arrays und Skalare dargestellt werden. Ein besonderes Beispiel war cumsum , das derzeit mit zweidimensionalen Arrays arbeitet, als ob ein Dimensionsargument von 1 angegeben würde. Dies steht im Widerspruch zu sum , bei dem anstelle eines Dimensionsarguments von 1 die Summe des gesamten Arrays zurückgegeben wird. Es verhindert auch, dass cumsum auf Covektoren in ähnlicher Weise wie auf Vektoren arbeitet. Ich denke, dass cumsum auf allgemeinen Arrays operieren sollte, indem kumulativ in N-dimensionaler Spalten-Hauptreihenfolge summiert wird. Im Allgemeinen sollten Dimensionsargumente nicht standardmäßig 1 sein.

Ich wäre etwas besorgt, wenn es keine Schnittstelle zur Identifizierung von Covektoren gäbe. Die meisten Operationen, z. B. size und ndims, würden sagen, dass sie genau wie andere 2d- oder 1d-Arrays sind. Nur wenn Sie zB ein Punktprodukt versuchen, sehen Sie einen Unterschied. AFAICT Sie müssten überprüfen, ob das Objekt ein RowVector ist, aber es ist sehr selten erforderlich, dass ein Objekt einen bestimmten Typ hat, um eine bestimmte allgemeine Eigenschaft zu haben.

@StefanKarpinski Dem stimme ich zu. Insbesondere sind diese Funktionen entweder "Array" -Operationen oder "lineare Algebra" -Operationen.

Ich habe hier mit einer Größe = (1, n) TransposedVector angefangen , genau wie Sie sagen. Ich habe eine Weile gebraucht, um eine gute Abdeckung der Tests und aller Kombinationen zu erhalten: * , \ , / für jedes mögliche c und t Methode und die mutierenden Methoden, während Mehrdeutigkeiten mit anderen Methoden in der Basis vermieden werden. Es ist eine langsame, aber systematische Arbeit, und ich denke, wir könnten sie zur Basis ziehen, wenn sie fertig ist (möglicherweise Dinge umbenennen).

Es gibt eine Unterscheidung mit Covector bei der es sich eigentlich um die konjugierte Transponierte handeln sollte (oder im allgemeinen Fall um eine noch allgemeinere Transformation!), Während es sich um eine RowVector oder TransposedVector ist konzeptionell einfacher.

@ JeffBezanson Gibt es etwas, das wir mit indices() tun können, um eine "Singleton" -Dimension zu haben?

Es ist jedoch sehr selten, dass ein Objekt einen bestimmten Typ haben muss, um eine bestimmte allgemeine Eigenschaft zu haben.

Richtig, es wäre cool, wenn dies eine Eigenschaft oder etwas wäre. Ich hatte gehofft, dass wir die lineare Algebra allgemeiner von Arrays trennen können, aber das ist eine große Veränderung. und könnte wahrscheinlich nicht ordentlich sein ohne eine nette Merkmalssyntax, die in Julia implementiert ist. Ich denke, hier besteht das Problem darin, dass wir drei Dinge (Vektoren, Covektoren und Matrizen) auf zwei Typen ( AbstractArray{1} und AbstractArray{2} ) abbilden, sodass einer von ihnen (Covektoren) zu einem speziellen Subtyp wird des anderen.

Ich hätte AbstractTransposedVector in das Paket aufgenommen, wenn ich mir einen Ort hätte überlegen können, an dem jemand etwas anderes als die grundlegende "Wrapper" -Implementierung benötigen würde.

@ JeffBezanson : Ich verstehe deine Besorgnis nicht. Die Idee ist, dass es sich wie ein abstraktes 1 × n 2d-Array verhält, mit Ausnahme von linearen Algebraoperationen, für die es sich wie der doppelte Raum von Spaltenvektoren verhält (der isomorph zu 1 × n 2d abstrakten Arrays ist). Wenn Sie nach einem Covektor suchen möchten, können Sie den Typ überprüfen, z. B. indem Sie darauf versenden.

Ein Update zu meinen Versuchen, dies in Angriff zu nehmen:

TransposedVectors.jl ist jetzt, glaube ich, "Feature abgeschlossen". Es enthält alle Maschinen, die erforderlich sind, um das zu tun, wovon @StefanKarpinski hier gesprochen hat - die Transponierung eines Vektors ist ein Wrapper eines Vektors, der sich wie ein zweidimensionales abstraktes Array mit einer Größe von 1xn verhält -, jedoch für lineare Algebraoperationen ( * , / , \ , ' , .' und norm ) verhält sich wie ein Zeilenvektor (oder Doppelvektor).

Sie können es so überprüfen:

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

Es kann zu Leistungseinbußen kommen - Benchmarking wäre hilfreich. In einigen Fällen werden weniger Kopien erstellt (die Transponierung ist faul) und in anderen Fällen werden mehr Kopien erstellt (manchmal wird beispielsweise eine konjugierte Kopie eines Vektors (niemals eine Matrix) in Ac_mul_Bc ). Und der Wrapper selbst hat leichte Kosten, bis wir veränderbare Felder in unveränderliche Felder einbinden (zum Glück funktioniert dies bereits gut mit StaticArrays.jl : smile :). Es gibt auch das Problem, sicherzustellen, dass es sich bei Bedarf um eine Art StridedArray .

Wenn Leute diesen Ansatz mögen, können wir sehen, dass bald eine PR erstellt wird, um Code auf Base zu migrieren (das Paket enthält bereits Komponententests und alle Unklarheiten wurden behoben). (Außerdem hatte @jiahao erwähnt, dass er mit einer eindimensionalen abstrakten Array-Version eines Doppelvektors experimentieren wollte, nicht sicher, ob Fortschritte

Denken die Leute, dass eine solche PR in Version 0.6 ohne einen Wrapper-Typ für transponierte Matrizen sinnvoll wäre? Während die transponierten Vektoren eine semantische Änderung darstellen, sind die transponierten Matrizen eher eine Optimierung, so dass ich annehme, dass sie separat eingehen könnten. Natürlich mag es "seltsam" erscheinen, wenn einige Transponierte Ansichten und andere Kopien sind - Meinungen? Ein weiterer zu berücksichtigender Punkt ist, dass, sobald sowohl transponierte Matrizen als auch Vektoren vorhanden sind, viel Arbeit erforderlich wäre, um alle At_mul_Bt -Funktionen zu entfernen, die durch Methoden für * , um das zu vervollständigen Übergang (obwohl sich die Vereinfachung lohnen wird!) - Ich bezweifle, dass irgendjemand in der Lage oder bereit ist, all das bis Ende dieses Monats zu tun ...

Großartige Arbeit, @andyferris! Haben Sie überhaupt mit einem generischen faulen Conjugate Wrapper experimentiert?

@andyferris Ich habe die Reifen getreten und mag das ziemlich. Scheint streng genommen eine Verbesserung zu sein, und ich hoffe, dass Leistungsprobleme so behandelt werden können, wie wir sie finden. Mal sehen, was andere zu sagen haben.

Danke Leute :)

Haben Sie überhaupt mit einem generischen faulen Conjugate Wrapper experimentiert?

Noch nicht, obwohl es ohne allzu großen Aufwand möglich sein könnte, aber um ehrlich zu sein, hatte ich Angst, bis Ende Dezember nicht so viel zu beenden. Mit Blick auf die Zukunft dachte ich darüber nach, dass wir am Ende möglicherweise die folgenden "unionall" -Typen für die lineare Algebra haben könnten:

  • AbstractVector
  • Conjugate{V where V <: AbstractVector} (oder Conj{V} , vielleicht sogar ConjArray ? Natürlich würde es Arrays jeder Dimension akzeptieren)
  • TransposedVector (oder RowVector ?)
  • TransposedVector{Conjugate{V where V <: AbstractVector}}
  • AbstractMatrix
  • Conjugate{M where M <: AbstractMatrix}
  • TransposedMatrix (oder einfach Transpose{M} ?)
  • TransposedMatrix{Conjugate{M where M<:AbstractMatrix}}

(plus alle gegebenen Merkmale des zugrunde liegenden Speichers, wie das, was wir jetzt DenseArray und StridedArray - ich hoffe, # 18457 könnte es etwas einfacher machen, diese auch auszudrücken).

Klingt das, abgesehen von der Benennung, nach einem vernünftigen Plan?

Was das sofortige Bikeshedding betrifft, bevorzugen wir für eine PR zur Basis dessen, was derzeit im Paket enthalten ist, TransposedVector , RowVector , einen generischen Transpose Wrapper für Vektoren und Matrizen , oder etwas anderes?

Ich denke, das Verhalten der Implementierung von @andyferris ist ziemlich gut und eine Verbesserung. Aber lassen Sie mich versuchen, meine Besorgnis etwas besser auszudrücken.

Das Problem besteht darin, neue Array-Typen zu definieren. Zum Beispiel ist DArray ein Subtyp von AbstractArray , also kann ein DArray auch ein AbstractVector oder ein AbstractMatrix . Im Idealfall erweitern wir die Unterscheidung zwischen Vektor und Matrix nur auf Zeile / Spalte / Matrix, sodass ein DArray ein AbstractRow , AbstractCol oder AbstractMatrix . Die Typhierarchie wäre so etwas wie

AbstractArray
    AbstractVector
        AbstractRowVector
        AbstractColumnVector
    AbstractMatrix
    ...

Ich habe gestern mit

Hier kann es einen gewissen Vorrang geben, da nicht alle AbstractVector als Spalten verwendet werden können, z.

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)

Dies könnte die Probleme beheben, die ich damals unter https://github.com/JuliaLang/julia/issues/4774#issuecomment -59428215 hatte

Das fehlen nur Klammern im Bereich?

Das ist nur ein Vorrang:

julia> randn(10,10) * (1.0:10.0)
10-element Array{Float64,1}:
 -22.4311
  ⋮

Ich stimme zu, dass die optimale Lösung hier Merkmale zu erfordern scheint, aber ich denke nicht, dass dies die Arbeit von

Ah, guter Punkt. Würde gerne eine falsche Leerzeichen-Priorität haben, wäre ein Syntaxfehler, ähnlich wie bei Fortress.

Die MIT-Gruppe wies darauf hin, dass eine Möglichkeit zur Implementierung der oben beschriebenen Typhierarchie darin besteht, der Array-Hierarchie einen Typparameter hinzuzufügen:

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}

Ich habe nicht alle Implikationen davon herausgearbeitet - wahrscheinlich ist das Schlimmste daran, dass Array{Int,1} ein abstrakter Typ wird -, aber dies scheint die Typhierarchie und die benötigten Abstraktionen ziemlich genau richtig zu machen.

Für das, was es wert ist, ist dies genau ein Anwendungsfall für unvollständig parametrisierte Supertypen; Sie können implizite Standardparameter werden.

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

Natürlich macht es den Versand etwas chaotisch… und es lohnt sich möglicherweise nicht, ihn zu unterstützen. Es ist nur ein Spuckball.

Das scheint mir gleichbedeutend mit Definition zu sein

type Array{T,N} <: AbstractArray{T,N,false}
end

Was wir hier vielleicht wollen, ist eine Art "Standardtypparameter", so dass der Typ Array{X,Y} bei dem X und Y keine freien Variablen haben, tatsächlich Array{X,Y,false} ergibt.

Eine andere Idee: Behalten Sie die Householder-Notation für innere Produkte v'v und multiplizieren Sie v'A indem Sie das Infix ' einem eigenen Operator machen, anstatt diese als v'*v und v'*A analysieren v' zur Identität, v*v einem Fehler und v*A einem Fehler könnte dies viel von dem geben, was gewünscht wird. Sie müssten äußere Produkte als outer(v,v) schreiben.

In einem solchen Schema wäre v' ein Noop oder sogar ein Fehler - da es keinen Grund gibt, einen Vektor in einem solchen Schema zu transponieren, wäre dies nur für eine Matrix sinnvoll.

@ JeffBezanson Ich bin damit einverstanden, dass ein AbstractRowVector am besten wäre (aber ich kann mir ehrlich gesagt keinen Anwendungsfall vorstellen, also habe ich ihn nicht im Paket implementiert). Ich möchte auch darauf hinweisen, dass dies als Subtyp von AbstractMatrix . Der Grund, warum ich mich in diese Richtung bewegt habe, ist, dass broadcast für Julia eher ein Kernkonzept zu sein scheint als die lineare Algebra, und die Leute erwarten, dass ein Zeilenvektor eine Zeile ist!

Natürlich ist es eine unglückliche Verwendung der Terminologie, RowVector <: AbstractMatrix ! Ich denke, dies resultiert daraus, dass 2D-Arrays und abstrakte Matrizen den gleichen Namen erhalten.

Ich habe das schon weit oben gesagt, aber da dieses Problem so lang ist, werde ich es noch einmal wiederholen: In einer generischen Sprache wie Julia muss die Eigenschaft "Array of Data" die Hauptüberlegung für AbstractArray . Wenn Sie antworten, wie sich broadcast für einen "transponierten" Vektor verhalten soll, erfahren Sie, ob es sich um 1D oder 2D handelt. Wenn Sie in Zeilen und Spalten denken möchten, sind 1 x n Zeilenvektoren am sinnvollsten. Wenn Sie duale Vektoren berücksichtigen möchten, ist 1D am sinnvollsten - und damit bin ich auch zufrieden! Das Dual eines Vektors zu nehmen ist jedoch komplizierter als das Umformen der Daten (z. B. müssen wir zumindest die Konjugation unterstützen).

Ich würde vermuten, dass das "Zeilen" -Bild den Erwartungen "typischer" Programmierer entspricht, während Leute mit fortgeschrittener mathematischer Ausbildung möglicherweise Dual-Vektoren besser nutzen würden, da es eine bessere Abstraktion ist (obwohl ich sicher bin, dass sie dazu in der Lage wären sympathisieren mit denen mit nur grundlegenden Kenntnissen der linearen Algebra). Also - auf welche Zielgruppe zielt Julia ab - Menschen mit mehr mathematischer Finesse als viele typische MATLAB-Benutzer oder Menschen mit weniger?

(Meiner Meinung nach war Julia das Ziel, da Julia eine "generische" Programmiersprache sein wollte.)

Da wir mögliche Typbäume diskutieren - in Zukunft mit Buffer und veränderbaren "Listen" - stelle ich mir einen abstrakten Baum von Schnittstellen vor, der etwas in der Art von

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

Natürlich könnten wir auch AbstractDualVector{T} <: AbstractArray{T,1} anstelle von AbstractRowVector .

Es wäre schwierig (und möglicherweise unnötig), einen flexiblen, konkreten Array -Typ zu haben, der all diesen Anforderungen entspricht. Merkmale würden es uns sicherlich ermöglichen, diese Unterschiede in unterstützten Schnittstellen leichter auszudrücken.

AbstractVector sowohl ein C ++ std::vector als auch ein linearer Algebra-Vektor zu sein, schien mir ein wenig frech :) (zumal die Array-Schnittstelle bedeutet, dass es niemals wirklich ein "abstrakter Vektor" in der sein kann linearer Algebra-Sinn (zB basenfrei))

Ja, es wäre gut, das Größenänderungsverhalten zu trennen. Dafür bin ich an Bord.

Diese Typhierarchie scheint zu implizieren, dass wir in Base separate konkrete Typen "Matrix" und "2D-Array" haben würden. Wir könnten versuchen, das zu umgehen, indem wir beispielsweise den Konstruktor für Array{T,2} tatsächlich einen anderen Matrixtyp zurückgeben lassen, aber es scheint ziemlich hässlich. Vielleicht verstehe ich das aber falsch.

Diese Typhierarchie scheint zu implizieren, dass wir in Base separate konkrete Typen "Matrix" und "2D-Array" haben würden.

Richtig ... Ich denke, um das gut zu machen, brauchen wir Eigenschaften. Denken Sie daran, ich nannte es einen "abstrakten Baum von Schnittstellen". Dies im Moment zu versuchen, würde etwas Hässliches erfordern, wie Sie sagten. Aber wir könnten wahrscheinlich AbstractList <: AbstractVector einfügen (und zugehörige Methoden verschieben), sobald Buffer fertig ist.

Im Moment ist die Verbindung zwischen unterstützten Schnittstellen und dem Typbaum ziemlich locker. Beim Versand ist es schwierig herauszufinden, ob ein (abstraktes) Array veränderbar ist (dh setindex! ), ob es in der Größe veränderbar ist, nur für Typen funktioniert, die in Base usw. definiert sind Für Benutzer ist es schwierig, eine Funktion mit Methoden bereitzustellen, die funktionieren und mit einer Vielzahl von Eingaben effizient sind (dies ist meine Erfahrung aus StaticArrays ).

Ein Gedanke für das Gespräch über mathematische Regionen der Typhierarchie und vernünftige Rollen für parametrisierte Abstraktionen. Wenn dies möglich ist, ist es für Julia eine gute Politik, die Trennung von Code, um das zu tun, was Fachwissen beabsichtigt, von dem, was fachmännisch ausgedrückt wird, zu vereinfachen und zu vereinfachen.

Eine Konvention, die wir wählen könnten, macht es üblich, einen anderen abstrakten Typ als Any zu verwenden, um eine ontotopologische Region zu gestalten, in der gleichzeitig konkrete Typen eine gemeinsame Ruhe finden. Dies würde zu sehr geringen Kosten eine größere multikontextuelle Sensibilität mit sich bringen. Wenn Sie einen Teil der Typhierarchie verstehen, können Sie sich anderen Teilen nähern.

Aus meiner Sicht handelt es sich um einen allgemeinen Rapportbringer. Eine erläuternde Abstraktion beleuchtet als nachbarschaftlich prägnante Konkretionen die generativen Ähnlichkeiten, die konstruieren. Mit Parametrisierungen, die die einfache Klarheit der Intuition tragen, erreicht Julia mit weniger Unsinn viel mehr.

OK, weitere Kommentare zu TransposedVectors sind willkommen. Ich denke, es wird ziemlich solide und bereit, in eine PR verwandelt zu werden, aber es gibt einige relevante Themen , die ich hier aufgelistet habe .

(Zum Beispiel: Ist RowVector ein besserer Name als TransposedVector ? Ist [1 2 3] ein RowVector oder ein Matrix ? Was ist indices(row, 1) ?

+1 für RowVector

Am 20. Dezember 2016, 7:01 Uhr, schrieb "Andy Ferris" [email protected] :

OK, weitere Kommentare zu TransposedVectors sind willkommen. ich fühle
Es wird ziemlich solide und bereit, in eine PR verwandelt zu werden, aber es gibt sie
Einige relevante Themen, die ich hier aufgelistet habe
https://github.com/andyferris/TransposedVectors.jl/issues .

(Zum Beispiel: Ist RowVector ein besserer Name als TransposedVector? Ist [1 2
3] ein RowVector oder eine Matrix? Was sind Indizes (Zeile 1)?)

- -
Sie erhalten dies, weil Sie kommentiert haben.
Antworte direkt auf diese E-Mail und sieh sie dir auf GitHub an
https://github.com/JuliaLang/julia/issues/4774#issuecomment-268170323 ,
oder schalten Sie den Thread stumm
https://github.com/notifications/unsubscribe-auth/AAm20YYqsXmprI23GgI5PYyWStpTOq5qks5rJ309gaJpZM4BMOXs
.

Ich würde wirklich gerne die Zeilen- / Spaltenkonvention heraushalten, und ich denke, es klingt komisch, Vector und RowVector (oder sogar ColVector und RowVector ). +1 für TransposedVector oder DualVector

@felixrehren Ich bin der Meinung, dass DualVector eine andere Semantik haben sollte als das, was ich implementiert habe, hauptsächlich eindimensional (mathematisch gesehen ist das Dual eines Vektors ein Vektor) und andere Dualitätseigenschaften haben (z. B. komplexe Konjugation). . Was in Ordnung ist, aber ich fand es etwas schwieriger zu implementieren und etwas weniger abwärtskompatibel.

Der Name TransposedVector ist in Ordnung. Aber es ist ein bisschen lang. Außerdem wird vorgeschlagen, dass ein Objekt dieses Typs nur durch Transponieren eines Vector . Aber ich nehme an, Sie könnten ein TransposedVector auf andere Weise verdienen, indem Sie beispielsweise eine Zeile einer Matrix extrahieren?

Ich denke, RowVector wäre ein guter Name - er ist prägnant, genau und intuitiv. @felixrehren , ich finde das Zeilen- / Spaltenbild hilfreich. Dies ist wahrscheinlich sogar unvermeidlich, da Sie bei jeder Verkettung oder anderen allgemeinen Array-Operationen (außer Multiplikation) überlegen müssen, in welche Richtung der Vektor ausgerichtet ist.

DualVector ist auch nicht schlecht, aber CoVector würde weniger formal klingen.

Die PR, die ich zuvor angedroht habe, ist jetzt bei # 19670 eingereicht. Ich habe mich vorerst für RowVector .

Aber ich nehme an, Sie könnten einen TransposedVector auch auf andere Weise erstellen, indem Sie beispielsweise eine Zeile einer Matrix extrahieren.

Dies ist tatsächlich ein Knackpunkt - ich habe noch nicht an eine großartige Syntax dafür gedacht. Irgendwelche Ideen?

Aber ich nehme an, Sie könnten einen TransposedVector auch auf andere Weise erstellen, indem Sie beispielsweise eine Zeile einer Matrix extrahieren.

Dies ist tatsächlich ein Knackpunkt - ich habe noch nicht an eine großartige Syntax dafür gedacht. Irgendwelche Ideen?

Während es auf den ersten Blick ansprechend erschien, dass matrix[scalar,range] (und andere ähnliche Konstrukte) einen Zeilenvektor ergeben, wäre dies eine signifikante Abweichung von der aktuellen Indizierungssemantik für mehrdimensionale Arrays, und Sonderfälle machen mich misstrauisch.

Stattdessen würde ich gerne sehen, dass RowVector (und Vector für diese Angelegenheit) jeden iterierbaren Typ in die entsprechende Art von Vektor konvertieren. Dann könnten Sie so etwas wie RowVector(matrix[scalar,range]) tun, was ziemlich klar wäre und das aktuelle Verhalten der Array-Indizierung nicht stört.

Richtig, die i -te Zeile kann zeilenweise von A[i,:].' extrahiert werden, derzeit in Version 0.5 und würde dies auch in Zukunft tun (um RowVector oder Transpose{V<:AbstractVector} oder was auch immer wir uns irgendwann einigen (siehe hier für die laufende Diskussion)). Vielleicht ist das die Antwort.

Wie wäre es, wenn Sie Base nur zwei neue Funktionen hinzufügen?
row(A,i) und col(A,i)
Letzteres wird nicht benötigt, sondern nur für die Symmetrie. Es ist prägnant, klar und enthält so viele Zeichen wie A[i,:].'

@benninkrs das macht Sinn. Im Gegensatz dazu ist meine intuitive Interpretation die lineare Algebra, bei der ein Vektor überhaupt keine Orientierung hat. Alle Standpunkte dazu sind vernünftig. Ich mag Vector und RowVector einfach nicht zusammen, weil die Benennung so aussieht, als würde sie ein abstraktes und ein konkretes Paradigma vermischen.

An diesem Punkt denke ich, dass wir entweder etwas tun müssen, das auf der Implementierung von uns entscheiden, Vektortransponierungen nicht ernst zu nehmen. Als Beispiel für eine Alternative sehen Sie hier einen Ansatz, der die schlimmsten Symptome behandelt: Lassen Sie v' wie es derzeit ist - dh erstellen Sie eine Zeilenmatrix -, aber analysieren Sie a'b als Infix-Operator mit Vorrang direkt darunter Multiplikation. Mit anderen Worten, wir hätten die folgenden Verhaltensweisen:

  1. v' ist eine Zeilenmatrix (ctranspose)
  2. v'v ist ein Skalar (Punktprodukt)
  3. v'*v ist ein 1-Element-Vektor (Zeilenmatrix * Vektor)
  4. v*v' ist eine äußere Produktmatrix (Vektor * Zeilenmatrix)
  5. v'A ist eine Zeilenmatrix ("vecmat")
  6. v'*A ist eine Zeilenmatrix (Matmat)
  7. v'A*v ist ein Skalar (matvec A * v dann Punktprodukt)
  8. (v'A)*v ist ein 1-Element-Vektor (vecmat then matvec)
  9. v'*A*v ist ein 1-Element-Vektor (matmat dann matvec)
  10. v'' ist eine Spaltenmatrix (Vektor ctranspose, dann Matrix ctranspose)

Im aktuellen Setup sind 2 und 3 äquivalent und 7, 8 und 9 sind äquivalent, was durch diese Änderung unterbrochen wird. Entscheidend ist jedoch, dass die kühnen Gegenstände diejenigen sind, nach denen die Menschen normalerweise greifen, da sie die kürzeste und bequemste der ähnlichen Formen sind - und sie alle tun, was wir von ihnen erwarten. Keine neuen Typen, nur ein neuer Infix-Operator. Der Hauptnachteil ist 10 - v'' ist immer noch eine Spaltenmatrix. Dies könnte wohl als Vorteil angesehen werden, da Postfix '' der Operator zum Umwandeln eines Vektors in eine Spaltenmatrix ist.

Wenn wir einen Schritt zurücktreten, haben wir gelernt, dass mehrdimensionale Arrays ohne zusätzliche Funktionen zur Kennzeichnung von Auf- und Abwärts- oder Dimensionsbeschriftungen oder zur Behandlung von ≤ 2 Dimensionen als fungibel wie Matlab nicht wirklich dazu gebracht werden können, reibungslos mit der linearen Algebra zu verzahnen. Was uns also bleibt, ist eine Frage der Bequemlichkeit - können wir es Menschen ermöglichen, gemeinsame lineare Algebraoperationen auf Vektoren und Matrizen bequem auszudrücken, ohne Arrays übermäßig zu komplizieren? Ich bin nicht fest entschlossen, diesen Ansatz zu verfolgen, aber ich denke, wir sollten diesen und andere Ansätze, die sich mit syntaktischer Bequemlichkeit befassen, ernsthaft in Betracht ziehen, ohne unsere Hierarchie der Array-Typen zu sehr durcheinander zu bringen.

Ein anderer Ansatz wäre, das Infix a'b speziell zu analysieren (knapp unter * ) und v' einen konjugierten Vektor zurückgeben zu lassen. Im Allgemeinen könnte das Postfix A' ein Array konjugieren und seine Dimensionen träge umkehren, während A.' die Dimensionen eines Arrays nur träge umkehren würde und somit als Identität für Vektoren fungiert. In diesem Schema kann die Liste der Eigenschaften wie folgt aussehen:

  1. v' ist ein Vektor (konjugiert)
  2. v'v ist ein Skalar (Punktprodukt)
  3. v'*v ist ein Fehler (empfehlen v'v für das innere Produkt und outer(v,v) für das äußere Produkt) †
  4. v*v' ist ein Fehler (empfehlen v'v für das innere Produkt und outer(v,v) für das äußere Produkt) †
  5. v'A ist ein Vektor (vecmat)
  6. v'*A ist ein Fehler (empfehlen Sie v'A für vecmat)
  7. v'A*v ist ein Skalar (matvec A * v dann Punktprodukt)
  8. (v'A)*v ist ein Fehler (empfehlen v'v für das innere Produkt und outer(v,v) für das äußere Produkt) †
  9. v'A'v ist ein Skalar ( v'(A'v) - konjugiertes Matvec dann inneres Produkt)
  10. v'' ist ein Vektor ( v'' === v und v.' === v )

Nachdem ich nun alle diese Eigenschaften aufgeschrieben habe, ist dies möglicherweise die bevorzugte Option: Alle Fehlerfälle dienen tatsächlich dazu, den Menschen zu helfen, bevorzugte Syntaxen zu erkennen und zu verwenden, und natürlich hat sie die wünschenswerte Eigenschaft v'' === v (und passt gut zusammen) wobei .' ein generischer Dimensionsumkehroperator ist). Sehr ähnliche Syntaxen zu haben, die sich nur geringfügig unterscheiden, ist wahrscheinlich verwirrender.

† Wir könnten diese möglicherweise zur Analysezeit abfangen, um genauere Fehler zu erhalten, und es besteht die Gefahr, dass Fehler in Fällen auftreten, in denen der Benutzercode ' und * überladen hat, damit diese Vorgänge funktionieren. Ich glaube, ein fauler konjugierter Wrapper kann notwendig sein, um diese Empfehlungen zu korrigieren, aber das brauchen wir trotzdem für # 5332.

Wenn wir einen Schritt zurücktreten, haben wir gelernt, dass mehrdimensionale Arrays ohne zusätzliche Funktionen zur Kennzeichnung von Auf- und Abwärts- oder Dimensionsbeschriftungen oder zur Behandlung von ≤ 2 Dimensionen als fungibel wie Matlab nicht wirklich dazu gebracht werden können, reibungslos mit der linearen Algebra zu verzahnen. Was uns also bleibt, ist eine Frage der Bequemlichkeit - können wir es Menschen ermöglichen, gemeinsame lineare Algebraoperationen auf Vektoren und Matrizen bequem auszudrücken, ohne Arrays übermäßig zu komplizieren? Ich bin nicht fest entschlossen, diesen Ansatz zu verfolgen, aber ich denke, wir sollten diesen und andere Ansätze, die sich mit syntaktischer Bequemlichkeit befassen, ernsthaft in Betracht ziehen, ohne unsere Hierarchie der Array-Typen zu sehr durcheinander zu bringen.

:100:

Das explizite Erstellen ' generischen Array-Operationen nach Postfix .' (anstelle von linearer Algebra) umgeht die Verdoppelung der Verschmelzung von Speicher- und linearen Algebra-Typen und lässt die Tür für Frameworks offen, die weniger Kompromisse beinhalten. In der Zwischenzeit sollte die Fähigkeit dieser Operationen, die Notation der Haushalte in den meisten Fällen zu simulieren, den größten Teil des gewünschten Komforts bieten. Auch weniger Code und Komplexität. Beste!

Ein Problem mit v.' als No-Op ist, dass A .+ v.' die Bedeutung vom Hinzufügen der Werte von v zu jeder Spalte von A zum Hinzufügen der Werte ändern würde zu den Zeilen von A . Dies würde es im Allgemeinen schwieriger machen, zeilenartige Operationen mit Vektoren durchzuführen, und es würde definitiv einen vollständigen Verfallszyklus erfordern, um zu vermeiden, dass Code stillschweigend das Falsche tut (in Fällen wie diesen, in denen A zufällig quadratisch ist).

An diesem Punkt denke ich, dass wir entweder etwas tun müssen, das auf der Implementierung von uns entscheiden, Vektortransponierungen nicht ernst zu nehmen.

Mir ist klar, dass die Frist für Version 0.6 fast abgelaufen ist, aber ich würde davor warnen, das Baby mit dem Badewasser wegzuwerfen. In diesem Stadium scheint es, dass die erwähnten RowVector plus Ansichten für Matrixtransponierung und Array-Konjugation Folgendes ermöglichen:

  • IMO, etwas vernünftigere lineare Algebra-Typen (wir leugnen nicht die Existenz von Doppelvektoren wie jetzt, obwohl ein Zeilenvektor als ein Sonderfall eines Doppelvektors angesehen werden könnte)
  • v'' === v
  • Einige der Dinge auf Stefans Liste wie v1'v2 sind ein Punktprodukt
  • Fast abwärtskompatible Array-Semantik - z. B. ist das Ergebnis von size(v') unverändert, aber wir haben v'' als eindimensional
  • Lazy Conj- und Transponierungs-Wrapper können unter bestimmten Umständen die Leistung steigern und trotzdem nützlich sein
  • Entfernen aller Ac_mul_Bc -Funktionen zugunsten von * und A_mul_B! (und ähnlich für \ , / ).

Es würde nicht allzu viel Mühe kosten, all dies für Array Laufen zu bringen (für mich besteht das Problem darin, zu dieser Jahreszeit Zeit zu finden und alle anderen Typen zu jagen, die wir in unserer linearen Algebra-Suite haben). Und der letzte Punkt wäre ein Seufzer der Erleichterung.

Auf der anderen Seite - IMHO scheinen diese Regeln das Parsen etwas zu erschweren und könnten ein wenig verwirrend und / oder überraschend sein, wie sie ' mit * (3, 4, 6 und 8 würden damit funktionieren RowVector ).

Und ja, wir müssten v.' oder so etwas verwerfen, um mögliche Fehler hervorzuheben. An diesem Punkt scheint es fast besser, v.' einem fehlenden Methodenfehler zu machen (wir unterstützen einfach keine Zeile / duale Vektoren, hält aber ein Paket nicht davon ab, wenn sie dies wünschen)

19670 sieht entweder bereit oder nah dran aus, wenn es Appetit gibt, etwas in v0.6 zu schleichen.

BAM

Woot.

War dies unser längster Themen-Thread?

Nein, # 11004 hat mehr

Es tut uns leid. Sie haben Recht, ich hätte einen offenen Issue-Thread angeben sollen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen