Julia: Matrixtransponierungen ernst nehmen

Erstellt am 10. März 2017  ·  141Kommentare  ·  Quelle: JuliaLang/julia

Derzeit ist transpose rekursiv. Das ist ziemlich unintuitiv und führt zu diesem Unglück:

julia> A = [randstring(3) for i=1:3, j=1:4]
3×4 Array{String,2}:
 "J00"  "oaT"  "JGS"  "Gjs"
 "Ad9"  "vkM"  "QAF"  "UBF"
 "RSa"  "znD"  "WxF"  "0kV"

julia> A.'
ERROR: MethodError: no method matching transpose(::String)
Closest candidates are:
  transpose(::BitArray{2}) at linalg/bitarray.jl:265
  transpose(::Number) at number.jl:100
  transpose(::RowVector{T,CV} where CV<:(ConjArray{T,1,V} where V<:(AbstractArray{T,1} where T) where T) where T) at linalg/rowvector.jl:80
  ...
Stacktrace:
 [1] transpose_f!(::Base.#transpose, ::Array{String,2}, ::Array{String,2}) at ./linalg/transpose.jl:54
 [2] transpose(::Array{String,2}) at ./linalg/transpose.jl:121

Seit einiger Zeit fordern wir die Leute auf, stattdessen permutedims(A, (2,1)) zu machen. Aber ich denke, wir alle wissen tief im Inneren, dass das schrecklich ist. Wie sind wir hierher gekommen? Nun, es ist ziemlich gut verstanden, dass man möchte, dass das ctranspose oder "Adjoint" einer Matrix von Matrizen rekursiv ist. Ein motivierendes Beispiel ist, dass Sie komplexe Zahlen mit 2x2-Matrizen darstellen können. In diesem Fall ist das "Konjugat" jedes "Elements" (eigentlich eine Matrix) sein Adjunkt als Matrix - mit anderen Worten, wenn ctranspose rekursiv ist, dann alles funktioniert. Dies ist nur ein Beispiel, aber es verallgemeinert.

Die Argumentation scheint folgende gewesen zu sein:

  1. ctranspose sollte rekursiv sein
  2. ctranspose == conj ∘ transpose == conj ∘ transpose
  3. transpose sollte daher auch rekursiv sein

Ich denke, hier gibt es ein paar Probleme:

  • Es gibt keinen Grund, warum ctranspose == conj ∘ transpose == conj ∘ transpose halten muss, obwohl der Name dies fast unvermeidlich erscheinen lässt.
  • Das Verhalten von conj das auf Arrays elementweise arbeitet, ist eine Art unglücklicher Überbleibsel von Matlab und keine wirklich mathematisch vertretbare Operation, so wie exp die elementweise arbeitet, nicht wirklich mathematisch fundiert ist und was expm wäre eine bessere Definition.
  • Tatsächlich wird das Konjugat (dh der Adjunkt) jedes Elements verwendet, was impliziert, dass ctranspose rekursiv sein sollte. Ohne Konjugation gibt es keinen guten Grund für eine rekursive Transponierung.

Dementsprechend würde ich die folgenden Änderungen vorschlagen, um die Situation zu beheben:

  1. Benennen Sie ctranspose (auch bekannt als ' ) in adjoint - genau das macht diese Operation und befreit uns von der Implikation, dass sie conj ∘ transpose .
  2. Verwerfen Sie vektorisierte conj(A) auf Arrays zugunsten von conj.(A) .
  3. Fügen Sie adjoint (geb. ctranspose ) ein recur::Bool=true Schlüsselwortargument hinzu, das angibt, ob es sich rekursiv aufrufen soll. Standardmäßig ist dies der Fall.
  4. Fügen Sie transpose ein recur::Bool=false Schlüsselwortargument hinzu, das angibt, ob es sich rekursiv aufrufen soll. Standardmäßig ist dies nicht der Fall.

Dies würde uns zumindest Folgendes schreiben lassen:

julia> A.'
4×3 Array{String,2}:
 "J00"  "Ad9"  "RSa"
 "oaT"  "vkM"  "znD"
 "JGS"  "QAF"  "WxF"
 "Gjs"  "UBF"  "0kV"

Ob wir das weiter auf A' verkürzen könnten oder nicht, hängt davon ab, was wir mit conj und adjoint von Nicht-Zahlen (oder genauer gesagt, nicht real, nicht) machen wollen -Komplexwerte).

[Diese Ausgabe ist die zweite einer ω₁-Teil-Reihe.]

breaking decision linear algebra

Hilfreichster Kommentar

(OT: Ich freue mich schon darauf, "7-Tensoren ernst zu nehmen", die nächste Folge der äußerst erfolgreichen 6-teiligen Miniserie ...)

Alle 141 Kommentare

Der logische Nachfolger einer früheren Ausgabe ... 👍

Das Verhalten von Conj, das elementar auf Arrays arbeitet, ist eine Art unglücklicher Überbleibsel von Matlab und keine wirklich mathematisch vertretbare Operation

Dies ist überhaupt nicht wahr und überhaupt nicht analog zu exp . Komplexe Vektorräume und Konjugationen davon sind ein perfekt etabliertes mathematisches Konzept. Siehe auch https://github.com/JuliaLang/julia/pull/19996#issuecomment -272312876

Komplexe Vektorräume und deren Konjugation sind ein perfekt etabliertes mathematisches Konzept.

Wenn ich mich nicht irre, ist die korrekte mathematische Konjugationsoperation in diesem Zusammenhang ctranspose und nicht conj (was genau mein Punkt war):

julia> v = rand(3) + rand(3)*im
3-element Array{Complex{Float64},1}:
 0.0647959+0.289528im
  0.420534+0.338313im
  0.690841+0.150667im

julia> v'v
0.879291582684847 + 0.0im

julia> conj(v)*v
ERROR: DimensionMismatch("Cannot multiply two vectors")
Stacktrace:
 [1] *(::Array{Complex{Float64},1}, ::Array{Complex{Float64},1}) at ./linalg/rowvector.jl:180

Das Problem bei der Verwendung eines Schlüsselworts für recur besteht darin, dass bei der letzten Überprüfung ein großer Leistungsverlust für die Verwendung von Schlüsselwörtern aufgetreten ist. Dies ist ein Problem, wenn Sie diese Funktionen rekursiv mit einem Basisfall aufrufen, der am Ende endet Skalare.

Wir müssen das Keyword-Leistungsproblem ohnehin in 1.0 beheben.

@StefanKarpinski , du

Selbst wenn Sie einen Hilbert-Raum haben, unterscheidet sich das komplexe Konjugat vom Adjunkt. zB ist das Konjugat eines komplexen Spaltenvektors in ℂⁿ ein anderer komplexer Vektor, aber der Adjunkt ist ein linearer Operator (ein "Zeilenvektor").

(Komplexe Konjugation bedeutet nicht , dass Sie conj(v)*v multiplizieren können!)

Das Abwerten von vektorisierten conj ist unabhängig vom Rest des Vorschlags. Können Sie einige Referenzen für die Definition der komplexen Konjugation in einem Vektorraum angeben?

https://en.wikipedia.org/wiki/Complexification#Complex_conjugation

(Diese Behandlung ist eher formal; aber wenn Sie "komplexe konjugierte Matrix" oder "komplexer konjugierter Vektor" googeln, werden Sie zig Verwendungen finden.)

Wenn die Zuordnung eines komplexen Vektors zum konjugierten Vektor (was conj jetzt tut) eine wichtige Operation ist, die sich von der Zuordnung zu einem konjugierten Covektor unterscheidet (was ' tut), können wir conj sicher behalten conj und adjoint da sie sich auf Skalare einigen würden, sich aber auf Arrays unterschiedlich verhalten würden.

Sollte conj(A) in diesem Fall conj für jedes Element aufrufen oder adjoint aufrufen? Die Darstellung komplexer Zahlen als Beispiel für 2x2-Matrizen würde vorschlagen, dass conj(A) tatsächlich adjoint für jedes Element aufrufen sollte, anstatt conj aufzurufen. Das würde adjoint , conj und conj. verschiedenen Operationen machen:

  1. adjoint : Tauschen Sie die Indizes i und j und ordnen Sie adjoint rekursiv den Elementen zu.
  2. conj : Map adjoint über Elementen.
  3. conj. : Map conj über Elementen.

conj(A) sollte für jedes Element conj aufrufen. Wenn Sie komplexe Zahlen durch 2x2-Matrizen darstellen, haben Sie einen anderen komplexierten Vektorraum.

Eine übliche Verwendung der Konjugation von Vektoren ist beispielsweise die Analyse von Eigenwerten realer Matrizen : Die Eigenwerte und Eigenvektoren kommen in komplex-konjugierten Paaren vor. Angenommen, Sie haben eine Blockmatrix, die durch ein 2d-Array A aus echten 2x2-Matrizen dargestellt wird und auf blockierte Vektoren v einwirkt, die durch 1d-Arrays aus 2-Komponenten-Vektoren dargestellt werden. Für jeden Eigenwert λ von A mit Eigenvektor v erwarten wir einen zweiten Eigenwert conj(λ) mit Eigenvektor conj(v) . Dies funktioniert nicht, wenn conj adjoint rekursiv

(Beachten Sie, dass sich der Adjunkt selbst für eine Matrix möglicherweise von einer konjugierten Transponierten unterscheidet, da der Adjunkt eines linearen Operators , der am allgemeinsten definiert ist, auch von der Wahl des inneren Produkts abhängt. Es gibt viele reale Anwendungen, bei denen einige Eine Art gewichtetes inneres Produkt ist angemessen. In diesem Fall ändert sich die geeignete Art und Weise, den Adjunkt einer Matrix zu nehmen. Ich stimme jedoch zu, dass wir bei einem Matrix den Adjunkt nehmen sollten, der dem Standard-Innenprodukt von entspricht dot(::Vector,::Vector) . Es ist jedoch durchaus möglich, dass einige AbstractMatrix (oder andere Typen mit linearem Operator) adjoint überschreiben möchten, um etwas anderes zu tun.)

Dementsprechend gibt es auch einige Schwierigkeiten bei der Definition eines algebraisch vernünftigen adjoint(A) für z. B. 3D-Arrays, da wir 3D-Arrays nicht als lineare Operatoren definiert haben (z. B. gibt es keine integrierte array3d * array2d -Operation). . Vielleicht sollte adjoint standardmäßig nur für Skalare, 1d- und 2d-Arrays definiert werden?

Update: Oh, gut: Wir definieren ctranspose jetzt auch nicht für 3D-Arrays. Fortfahren.

(OT: Ich freue mich schon darauf, "7-Tensoren ernst zu nehmen", die nächste Folge der äußerst erfolgreichen 6-teiligen Miniserie ...)

Wenn Sie komplexe Zahlen durch 2x2-Matrizen darstellen, haben Sie einen anderen komplexierten Vektorraum.

Ich folge dem nicht - die 2x2-Matrixdarstellung komplexer Zahlen sollte sich genauso verhalten wie komplexe Skalare als Elemente. Ich würde denken, zB das, wenn wir definieren

m(z::Complex) = [z.re -z.im; z.im z.re]

und wir haben einen beliebigen komplexen Vektor v dann möchten wir, dass diese Identität gilt:

conj(m.(v)) == m.(conj(v))

Ich würde das Beispiel genauer formulieren und einen Vergleich mit ' anstellen , der bereits mit m pendeln soll , aber ich kann es wegen https://github.com/JuliaLang/julia/ nicht. Ausgaben / 20979 , die versehentlich diese Kommutierung m.(v)' == m.(v') anhalten , aber wenn ich Sie richtig verstehe, conj(m.(v)) == m.(conj(v)) nicht?

conj(m(z)) sollte == m(z) .

Ihr m(z) ist ein Isomorphismus zu komplexen Zahlen unter Addition und Multiplikation, aber es ist auf andere Weise nicht dasselbe Objekt für die lineare Algebra, und wir sollten nicht so tun, als wäre es für conj . Wenn beispielsweise z ein komplexer Skalar ist, dann eigvals(z) == [z] , aber eigvals(m(z)) == [z, conj(z)] . Wenn der Trick m(z) auf komplexe Matrizen ausgedehnt wird, hat diese Verdoppelung des Spektrums schwierige Konsequenzen für z. B. iterative Methoden (siehe z. B. dieses Dokument ).

Eine andere Art, es auszudrücken, ist, dass z bereits ein komplexer Vektorraum ist (ein Vektorraum über den komplexen Zahlen), aber die 2x2-reelle Matrix m(z) ist ein Vektorraum über den reellen Zahlen (Sie) kann m(z) mit einer reellen Zahl multiplizieren) ... wenn Sie m(z) "komplexieren", indem Sie sie mit einer komplexen Zahl multiplizieren, z. B. m(z) * (2+3im) ( nicht m(z) * m(2+3im) ), dann Sie Holen Sie sich einen anderen komplexen Vektorraum, der nicht mehr isomorph zum ursprünglichen komplexen Vektorraum ist z .

Dieser Vorschlag scheint eine echte Verbesserung zu bewirken: +1: Ich denke über die mathematische Seite nach:

  • Soweit ich weiß, ist die Konjugation eine Aktion am Ring (sprich: Zahlen), die die Koeffizienten für unseren Vektorraum / unsere Vektoren liefert. Diese Aktion induziert eine weitere Aktion auf den Vektorraum (und auf seine Abbildungen, gelesen: Matrizen), die auch als Konjugation bezeichnet wird, durch die Linearität der Konstruktion des Vektorraums über dem Ring. Daher funktioniert die Konjugation notwendigerweise durch elementweise Anwendung auf die Koeffizienten. Für Julia bedeutet dies, dass im Herzen für einen Vektor v , conj(v) = conj.(v) und es eine Designentscheidung ist, ob conj Methoden auch für Arrays oder nur für Skalare hat, die beide scheinen ok? (Stevens Argument zum Beispiel der komplexen Zahlen als 2x2-Matrizen ist, dass dies ein Vektorraum ist, dessen Koeffizienten formal / tatsächlich real sind, sodass die Konjugation hier keine Auswirkung hat.)
  • adjoint hat eine algebraische Bedeutung, die in vielen wichtigen Bereichen, die eng mit der linearen Algebra verbunden sind, von grundlegender Bedeutung ist (siehe 1 und 2 und 3 , alle mit großen Anwendungen auch in der Physik). Wenn adjoint hinzugefügt wird, sollten Schlüsselwortargumente für die betrachtete Aktion zulässig sein. In einer Vektorräume / Funktionsanalyse wird diese Aktion normalerweise durch das innere Produkt induziert, daher kann das Schlüsselwortargument stattdessen die Form sein. Was auch immer für Julias Transponierungen gedacht ist, sollte Konflikte mit diesen Anwendungen vermeiden, also denke ich, dass adjoint ein bisschen hoch aufgeladen ist?

@felixrehren , ich glaube nicht, dass Sie ein Schlüsselwortargument verwenden würden, um das innere Produkt anzugeben, das den Adjunkt induziert. Ich denke, Sie würden stattdessen einfach einen anderen Typ verwenden, genauso als ob Sie die Bedeutung von dot für einen Vektor ändern möchten.

Meine Präferenz wäre etwas einfacher:

  • Behalten Sie conj unverändert (elementweise).
  • Stellen Sie sicher, dass a' adjoint(a) , und machen Sie es immer rekursiv (und schlagen Sie daher für Arrays von Zeichenfolgen usw. fehl, bei denen adjoint für die Elemente nicht definiert ist).
  • Stellen Sie sicher, dass a.' transpose(a) , und machen Sie es niemals rekursiv (nur ein permutedims ), und ignorieren Sie daher den Typ der Elemente.

Ich sehe wirklich keinen Anwendungsfall für ein nicht rekursives adjoint . Ich kann mir Anwendungsfälle für ein rekursives transpose vorstellen, aber in jeder Anwendung, in der Sie a.' in transpose(a, recur=true) ändern müssen, scheint dies genauso einfach zu sein Rufen Sie eine andere Funktion transposerecursive(a) . Wir könnten transposerecursive in Base definieren, aber ich denke, die Notwendigkeit dafür wird so selten sein, dass wir abwarten sollten, ob es tatsächlich auftaucht.

Ich persönlich bin der Meinung, dass es für Benutzer (und für die Implementierung) am einfachsten ist, dies einfach zu halten, und in Bezug auf die lineare Algebra immer noch recht vertretbar ist.

Bisher sind (die meisten) unserer linearen Algebra-Routinen ziemlich stark auf Standard-Array-Strukturen und das Standard-Innenprodukt verwurzelt. In jeden Slot Ihrer Matrix oder Ihres Vektors setzen Sie ein Element Ihres "Feldes". Ich werde argumentieren, dass wir uns für Elemente von Feldern um + , * usw. und conj kümmern, aber nicht um transpose . Wenn Ihr Feld eine spezielle komplexe Darstellung war, die ein bisschen wie eine 2x2-Realmatrix aussieht, sich aber unter conj ändert, dann ist das in Ordnung - es ist eine Eigenschaft von conj . Wenn es sich nur um ein 2x2 echtes AbstractMatrix , das sich unter conj nicht ändert, wohl nicht unter dem Adjoint ändern ( @stevengj sagte es besser, als ich beschreiben kann - Es mag einen Isomorphismus zu komplexen Zahlen geben, aber das bedeutet nicht, dass er sich in jeder Hinsicht gleich verhält.

Wie auch immer, das 2x2-Komplexbeispiel fühlt sich für mich ein bisschen wie ein roter Hering an. Die eigentliche Ursache für das rekursive Matrixverhalten war eine Abkürzung zur linearen Algebra auf Blockmatrizen. Warum behandeln wir diesen Sonderfall nicht mit der richtigen Sorgfalt und vereinfachen das zugrunde liegende System?

Mein "vereinfachter" Vorschlag wäre also:

  • Behalten Sie conj (oder machen Sie eine Ansicht für AbstractArray s)
  • Machen Sie a' einer nicht rekursiven Ansicht, sodass (a')[i,j] == conj(a[j,i])
  • Machen Sie a.' einer nicht rekursiven Ansicht, sodass (a.')[i,j] == a[j,i]
  • Führen Sie einen BlockArray -Typ für die Verarbeitung von Blockmatrizen usw. ein (in Base oder möglicherweise in einem gut unterstützten Paket). Dies wäre wohl viel leistungsfähiger und flexibler als ein Matrix{Matrix} für diesen Zweck, aber ebenso effizient.

Ich denke, diese Regeln wären einfach genug, damit Benutzer sie annehmen und darauf aufbauen können.

PS - @StefanKarpinski Aus praktischen Gründen

Ich habe es auch an anderer Stelle erwähnt, aber ich werde es der Vollständigkeit halber hier hinzufügen: Rekursive Transponierungsansichten haben die ärgerliche Eigenschaft, dass sich der Elementtyp im Vergleich zu dem Array, das er umschließt, ändern kann. ZB transpose(Vector{Vector}) -> RowVector{RowVector} . Ich habe nicht über eine Möglichkeit nachgedacht, diesen Elementtyp für RowVector ohne Laufzeitstrafe oder durch Aufrufen von Inferenz zur Berechnung des Ausgabetyps zu erhalten. Ich vermute, dass das aktuelle Verhalten (das Aufrufen von Schlussfolgerungen) aus sprachlicher Sicht unerwünscht ist.

NB: Es gibt auch nichts, was Benutzer davon abhält, conj zu definieren, um einen anderen Typ zurückzugeben. Daher leidet auch die Ansicht ConjArray unter diesem Problem, unabhängig davon, ob die Transposition rekursiv ist oder nicht.

@stevengj - Sie weisen darauf hin, dass "komplexe" 2x2-Matrizen eher ein formal realer Vektorraum als ein komplexer Vektorraum sind, aber dieser Punkt stellt für mich die ursprüngliche Motivation für einen rekursiven Adjunkt in Frage, was mich zu der Frage führt, ob @andyferris 'Vorschlag wäre nicht besser (nicht rekursive Transponierung und Adjunkt). Ich denke, die Tatsache, dass sowohl das komplexe 2x2-Beispiel als auch die Blockmatrixdarstellung "wollen" rekursiv sein sollen, ist suggestiv, aber angesichts Ihrer Kommentare zu diesem ersten Beispiel muss ich mich fragen, ob es keine anderen Fälle gibt, in denen nicht rekursiver Adjunkt vorhanden ist ist korrekter / bequemer.

Wenn der Adjoint nicht rekursiv ist, ist er kein Adjoint. Es ist einfach falsch.

Können Sie das etwas mehr rechtfertigen, wenn Sie einen Moment Zeit haben?

Der Adjunkt eines Vektors muss ein linearer Operator sein, der ihn einem Skalar zuordnet. Das heißt, a'*a muss ein Skalar sein, wenn a ein Vektor ist. Und dies führt zu einem entsprechenden Zusatz für Matrizen, da die definierende Eigenschaft a'*A*a == (A'*a)'*a .

Wenn a ein Vektor von Vektoren ist, bedeutet dies, dass a' == adjoint(a) rekursiv sein muss.

OK, ich denke ich folge dem.

Wir haben auch rekursive innere Produkte:

julia> norm([[3,4]])
5.0

julia> dot([[3,4]], [[3,4]])
25

Es ist klar, dass das "Adjoint" oder "Dual" oder was auch immer ähnlich rekursiv sein sollte.

Ich denke, die zentrale Frage ist dann, benötigen wir a' * b == dot(a,b) für alle Vektoren a , b ?

Die Alternative ist zu sagen, dass ' nicht unbedingt den Adjoint zurückgibt - es ist nur eine Array-Operation, die die Elemente transponiert und sie durch conj leitet. Dies ist zufällig der Zusatz für die realen oder komplexen Elemente.

Es gibt nur einen Grund, warum wir in der linearen Algebra einen speziellen Namen und spezielle Symbole für Adjunkte haben, und das ist die Beziehung zu inneren Produkten. Das ist der Grund, warum "konjugierte Transponierung" eine wichtige Operation ist und "konjugierte Rotation von Matrizen um 90 Grad" nicht. Es macht keinen Sinn , etwas zu haben, das "nur eine Array-Operation ist, die Zeilen und Spalten austauscht und konjugiert", wenn es nicht mit Punktprodukten verbunden ist.

Man kann eine ähnliche Beziehung zwischen transpose und einem nicht konjugierten "Punktprodukt" definieren, die für eine rekursive Transponierung sprechen würde. Nicht konjugierte "Punktprodukte" für komplexe Daten, die überhaupt keine inneren Produkte sind, werden jedoch nicht annähernd so oft angezeigt wie echte innere Produkte - sie entstehen aus Bi-Orthogonalitäts-Beziehungen, wenn ein Operator in komplexe Daten geschrieben wird. symmetrische (nicht hermitische) Form - und haben nicht einmal eine eingebaute Julia-Funktion oder eine gemeinsame Terminologie in der linearen Algebra, wohingegen der Wunsch, Zeilen und Spalten beliebiger nicht numerischer Arrays auszutauschen, insbesondere beim Rundfunk weitaus häufiger vorkommt. Aus diesem Grund kann ich es unterstützen, transpose nicht rekursiv zu machen, während adjoint rekursiv bleibt.

Aha. Das Umbenennen von ' in adjoint wäre also Teil der Änderung, um deutlich zu machen, dass es sich nicht um conj ∘ transpose ?

Was mich immer mit rekursiven Arrays verwechselt hat, ist: Was ist in diesem Zusammenhang ein Skalar? In allen mir bekannten Fällen heißt es, die Elemente eines Vektors seien Skalare. Selbst in dem Fall, in dem ein Mathematiker eine Blockvektor- / Matrixstruktur auf ein Stück Papier schreibt, wissen wir immer noch, dass dies nur eine Abkürzung für einen größeren Vektor / eine größere Matrix ist, bei der die Elemente wahrscheinlich reelle oder komplexe Zahlen sind (dh BlockArray ). Sie erwarten, dass sich die Skalare unter * multiplizieren können, und der Typ des Skalars unterscheidet sich normalerweise nicht zwischen dem Vektor und dem Dualen.

@andyferris , für einen allgemeinen Vektorraum sind die Skalare ein Ring (Zahlen), mit dem Sie Vektoren multiplizieren können. Ich kann 3 * [[1,2], [3,4]] , aber ich kann nicht [3,3] * [[1,2], [3,4]] . Selbst für Array{Array{Number}} ist der richtige Skalartyp Number .

Einverstanden - aber die Elemente werden normalerweise als (dieselben) Skalare bezeichnet, nicht wahr?

Die Behandlungen, die ich sowieso gesehen habe, beginnen mit einem Ring und bauen daraus einen Vektorraum auf. Der Ring unterstützt + , * , aber ich habe keine Behandlung gesehen, bei der adjoint , dot oder was auch immer unterstützt werden muss.

(Entschuldigung, ich versuche nur, das zugrunde liegende mathematische Objekt zu verstehen, das wir modellieren.)

Es hängt davon ab, was Sie unter "Kurzschrift" und was Sie unter "Elementen" verstehen.

Zum Beispiel ist es durchaus üblich, Funktionsvektoren endlicher Größe und "Matrizen" unendlich dimensionaler Operatoren zu haben. Betrachten Sie z. B. die folgende Form der makroskopischen Maxwell-Gleichungen :

image

In diesem Fall haben wir eine 2x2-Matrix von linearen Operatoren (z. B. Locken), die auf 2-Komponenten-Vektoren wirken, deren "Elemente" 3-Komponenten-Vektorfelder sind. Dies sind Vektoren über dem Skalarfeld komplexer Zahlen. In gewissem Sinne sind die "Elemente" der Vektoren komplexe Zahlen - einzelne Komponenten der Felder an einzelnen Punkten im Raum -, wenn Sie weit genug nach unten gehen, aber das ist ziemlich trübe.

Oder genauer gesagt, können wir nicht immer den Ring verwenden, um die Koeffizienten des Vektors auf einer bestimmten Basis zu beschreiben (angesichts der Array-Natur der Dinge in Julia sind wir nicht basenfrei)?

In jedem Fall ist die Konsequenz immer noch, dass Adjunkte rekursiv sein müssen, nicht wahr? Ich verstehe nicht, an welchem ​​Punkt Sie sich befinden.

(Ich denke, wir können absolut basenfrei sein, da die Objekte oder Elemente der Arrays symbolische Ausdrücke wie SymPy oder eine andere Datenstruktur sein könnten, die ein abstraktes mathematisches Objekt darstellt, und der Adjunkt ein anderes Objekt zurückgeben könnte, das bei Multiplikation ein berechnet Integral zum Beispiel.)

Ich versuche nur besser zu verstehen, ohne einen bestimmten Punkt zu machen :)

Sicher ist der Adjunkt oben rekursiv. Ich habe mich gefragt, ob es zB oben am besten ist, sich [E, H] als BlockVector dessen (unendlich?) Viele Elemente komplex sind, oder als 2-Vektor, dessen Elemente Vektorfelder sind. Ich denke in diesem Fall wäre der BlockVector -Ansatz nicht praktikabel.

Trotzdem danke.

Vielleicht kann ich meine Gedanken in dieser Form destillieren:

Für einen Vektor v erwarte ich, dass length(v) die Dimension des Vektorraums beschreibt, dh die Anzahl der Skalarkoeffizienten, die ich zur (vollständigen) Beschreibung eines Elements des Vektorraums benötige, und auch, dass v[i] den i -ten Skalarkoeffizienten zurückgibt. Bisher habe ich AbstractVector als "abstrakten Vektor" betrachtet, sondern als ein Objekt, das die Koeffizienten eines Elements eines Vektorraums enthält, wenn der Programmierer eine Basis kennt.

Es schien ein einfaches und nützliches mentales Modell zu sein, aber vielleicht ist dies zu restriktiv / unpraktisch.

(BEARBEITEN: Es ist restriktiv, da sich in Vector{T} T wie ein Skalar verhalten muss, damit lineare Algebraoperationen funktionieren.)

aber ich kann nicht [3,3] * [[1,2], [3,4]]

Wir könnten das beheben. Ich bin mir nicht sicher, ob es eine gute Idee ist oder nicht, aber es könnte durchaus zum Funktionieren gebracht werden.

Ich bin mir nicht sicher, ob es wünschenswert ist ... in diesem Fall ist [3,3] eigentlich kein Skalar. (Wir haben auch bereits die Möglichkeit, [3,3]' * [[1,2], [3,4]] - was ich wirklich nicht im Sinne einer linearen Algebra interpretieren kann).

Dies zeigt einen interessanten Eckfall: Wir scheinen zu sagen, dass v1' * v2 das innere Produkt ist (und somit einen "Skalar" zurückgibt), aber [3,3]' * [[1,2], [3,4]] == [12, 18] . Ein erfundenes Beispiel, nehme ich an.

In diesem Fall [3,3] ein Skalar: Skalare sind Elemente in dem zugrunde liegenden Ring, und es gibt viele gute Möglichkeiten , Elemente der Form zu machen [a,b] in einen Ring (und damit die Definition Vektoren / Matrizen über). Die Tatsache, dass sie als Vektoren geschrieben sind oder dass dieser Ring selbst einen Vektorraum über einem anderen zugrunde liegenden Ring bildet, ändert daran nichts. (Sie können auch eine andere Notation verwenden, die die Vektoren verbirgt! Oder sie ganz anders aussehen lassen.) Wie @andyferris sagte , hängt ein Skalar vom Kontext ab.

Formal ist Rekursion nicht Teil des Adjoint. Stattdessen erledigt die Konjugation der Skalarelemente diesen Teil der Arbeit - und wenn ein Skalar s als Matrix dargestellt wird, wird bei der Konjugation von s die Matrix transponiert, die wir zur Bezeichnung verwenden es. Dies ist jedoch nicht immer der Fall, sondern hängt von der vom Benutzer vorgegebenen Struktur des Skalarrings ab.

Ich denke, das Erzwingen einer adjungierten Rekursion kann ein praktischer Kompromiss sein, der typisch für Anwendungen vom Typ Matlab ist. Aber für mich bedeutet es sehr ernst zu nehmen, nicht rekursiven Adjunkt zu verwenden und typisierte Skalare + Versand zu verwenden, damit conj die notwendige Magie auf die Struktur des zugrunde liegenden Rings ausübt.

In diesem Fall ist [3,3] ein Skalar: Skalare sind Elemente im zugrunde liegenden Ring

Nur dass ein solcher Skalar kein Element des Rings ist, das durch + , * . * wird nicht unterstützt. Ich kann nicht [3,3] * [3,3] .

Ich denke, das Erzwingen einer adjungierten Rekursion kann ein praktischer Kompromiss sein, der typisch für Anwendungen vom Typ Matlab ist. Aber für mich bedeutet es sehr ernst zu nehmen, nicht rekursiven Adjunkt zu verwenden und typisierte Skalare + Versand zu verwenden, damit conj die notwendige Magie auf die Struktur des zugrunde liegenden Rings ausübt.

Ich stimme zu, wenn wir ein praktisches Compise machen wollen, ist dies völlig in Ordnung und es ist das, was wir bisher gemacht haben, aber es scheint mir, dass die zugrunde liegenden Skalare die des vollständig abgeflachten Arrays sind und auf dem die lineare Algebra definiert ist . Wir haben die Technologie, um ein BlockVector und ein BlockMatrix (und BlockDiagonal usw.) zu erstellen, die eine effiziente, abgeflachte Ansicht des vollständig erweiterten Arrays bieten, also das "super ernsthafte" "Ansatz sollte zumindest machbar sein. Ich denke auch, dass es genauso benutzerfreundlich wäre wie der rekursive Ansatz (ein paar zusätzliche Zeichen, um BlockMatrix([A B; C D]) im Austausch für einen möglicherweise schöneren semantischen und möglicherweise besser lesbaren Code einzugeben - da bin ich mir sicher Punkte stehen zur Debatte). Es wäre jedoch mehr Arbeit, all dies umzusetzen.

@andyferris Sie haben Recht, sie sind kein Ring, weil * nicht definiert ist - aber ich denke, wir sind auf der gleichen Seite, auf der * leicht dafür definiert werden könnte. und es gibt viele verschiedene Möglichkeiten, um eine Ringstruktur zu erhalten. Ich nehme an, wir sind auf derselben Seite: Tippen ist die erweiterbarere Möglichkeit, dies zu lösen.

(Über Skalare: Die Skalare sind nicht unbedingt die Elemente der vollständig abgeflachten Struktur! Der Grund dafür ist. Ein eindimensionaler komplexer Vektorraum ist eindimensional über die komplexen Zahlen und zweidimensional über die reellen Zahlen; keine Darstellung ist es Quaternionen sind zweidimensional über komplexe Zahlen. Oktonionen sind zweidimensional über Quarternionen, vierdimensional über komplexe Zahlen und achtdimensional über den Realzahlen. Es gibt jedoch keinen Grund mehr darauf zu bestehen, dass Oktonionen nicht mehr "achtdimensional" sind als darauf zu bestehen, dass ihre Skalare die reellen Zahlen sind, die nicht komplex sind, das sind Entscheidungen, die nicht durch Logik erzwungen werden. Es ist nur eine Frage der Repräsentation, dem Benutzer sollte diese Freiheit gewährt werden, da die lineare Algebra in jedem Fall dieselbe ist. Und Sie erhalten viel längere Ketten solcher Räume mit [abgeschnittenen] Multipolynomringen oder algebraischen Erweiterungen von Zahlenfeldern, die beide lebhafte Anwendungen mit Matrizen haben. Julia muss nicht so weit in den Kaninchenbau gehen - aber wir sollte sich daran erinnern, dass es existiert, um es nicht zu blockieren. Vielleicht ein Diskursthema? :))

@felixrehren , ein Vektor von Vektoren über einem Ring R wird am besten als ein direkter [[1,2], [3,4]] .

Konjugation und Adjunkte sind normalerweise getrennte Konzepte; Zu sagen, dass "Konjugation der Skalarelemente" die Arbeit hier erledigen sollte, erscheint mir völlig falsch - das Gegenteil von "ernst" - außer in dem unten angegebenen Sonderfall.

Betrachten Sie den Fall von "2-Komponenten-Spaltenvektoren" (| u⟩, | v⟩) zweier Elemente | u⟩ und | v⟩ in einem Hilbert-Raum H über einem Ring (sagen Sie die komplexen Zahlen ℂ) in Dirac-Notation: "Vektoren von Vektoren." Dies ist ein Hilbert-Raum mit direkter Summe H⊕H mit dem natürlichen inneren Produkt ⟨(| u⟩, | v⟩), (| w⟩, | z⟩)⟩ = ⟨u | w⟩ + ⟨v | z⟩. Der Adjunkt sollte daher den linearen Operator erzeugen, der aus dem "Zeilenvektor" (⟨u | ⟨v |) besteht, dessen Elemente selbst lineare Operatoren sind: Der Adjunkt ist"rekursiv" . Dies unterscheidet sich grundlegend von dem komplexen Konjugat, das ein Element desselben Hilbert-Raums H⊕H erzeugt, das durch die Konjugation des Unterringes induziert wird.

Sie verwirren die Angelegenheit, indem Sie sich auf Vektoren über Quaternionen usw. beziehen. Wenn die "Elemente" des Vektors auch Elemente des zugrunde liegenden "komplexierten" Rings sind, dann sind natürlich der Adjunkt und das Konjugat dieser Elemente dasselbe. Dies gilt jedoch nicht für alle Direktproduktbereiche.

Anders ausgedrückt, der Objekttyp muss zwei verschiedene Informationen angeben:

  • Der Skalarring (daher das Konjugat, das ein Element desselben Raums erzeugt).
  • Das innere Produkt (daher der Adjunkt, der ein Element des dualen Raums erzeugt).

Für Vector{T<:Number} sollten wir lesen, dass der Skalarring T (oder complex(T) für den komplexierten Vektorraum) ist und dass das innere Produkt das übliche euklidische ist .

Wenn Sie einen Vektor von Vektoren haben, ist dies ein Hilbert-Raum mit direkter Summe, und der Ring ist die Förderung der Skalarringe der einzelnen Vektoren, und das Punktprodukt ist die Summe der Punktprodukte der Elemente. (Wenn die Skalare nicht gefördert werden können oder die Punktprodukte nicht summiert werden können, handelt es sich überhaupt nicht um einen Vektor / Hilbert-Raum.)

Wenn Sie einen skalaren T<:Number , dann conj(x::T) == adjoint(x::T) .

Wenn Sie also versuchen, komplexe Zahlen z::Complex{T} durch die 2x2-Matrizen m(z)::Array{T,2} darzustellen, zeigt Ihr Typ sowohl einen anderen Ring T als auch ein anderes inneres Produkt an als z , und daher sollten Sie nicht erwarten, dass entweder conj oder adjoint gleichwertige Ergebnisse liefern.

Ich verstehe irgendwie, was du sagst @felixrehren. Wenn der Skalar real ist, kann ein Okternian als 8-dimensionaler Vektorraum betrachtet werden. Wenn der Skalarkoeffizient jedoch ein Okternian war, gibt es eine triviale Dimension-1-Basis, die alle Octernianer darstellt. Ich bin mir nicht sicher, ob dies anders ist als das, was ich gesagt habe (oder was ich gemeint habe: lächeln :), wenn wir ein AbstractVector{T1} haben, könnte es isomorph zu einem AbstractVector{T2} einer anderen Dimension sein ( length ). Aber T1 und T2 sollten beide Ringe unter den Julia-Operatoren + und * (und der Isomorphismus könnte das Verhalten von + bewahren aber nicht * oder das innere Produkt oder den Zusatz).

@stevengj Ich habe immer an die direkte Summe gedacht, genau wie meine BlockVector . Sie haben zwei (disjunkte) unvollständige Basen. Innerhalb jeder Basis können Sie möglicherweise Überlagerungen wie c₁ | X₁⟩ + c₂ | X₂⟩ in der ersten Basis oder c₃ | Y₁⟩ + c₄ | Y₂⟩ in der zweiten Basis bilden. Die "direkte Summe" repräsentiert den Raum von Zuständen, die wie (c₁ | X₁⟩ + c₂ | X₂⟩) + (c₃ | Y₁⟩ + c₄ | Y₂⟩) aussehen. Es scheint mir, dass das einzige Besondere, das dies von einer Basis der Dimension vier über die komplexen Zahlen trennt (dh Zustände wie c₁ | X₁⟩ + c₂ | X₂⟩ + c₃ | Y₁⟩ + c₄ | Y₂⟩), die Klammern sind - für mich scheint es notational; Die direkte Summe ist nur eine bequeme Möglichkeit, diese auf Papier oder mit Arrays auf einem Computer zu schreiben (und kann nützlich sein, um z. B. Symmetrien zu katalogisieren und auszunutzen oder das Problem aufzuteilen (möglicherweise um es zu parallelisieren) usw. ). In diesem Beispiel ist X⊕Y immer noch ein vierdimensionaler Vektorraum, in dem die Skalare komplex sind.

Das ist es, was mich dazu bringt, eltype(v) == Complex{...} und length(v) == 4 anstatt eltype(v) == Vector{Complex{...}} und length(v) == 2 . Es hilft mir, den zugrunde liegenden Ring- und Vektorraum zu erkennen und zu begründen. Ist es nicht auch dieser "abgeflachte" Raum, in dem Sie eine "globale" Transposition und elementweise conj durchführen können, um den Adjunkt zu berechnen?

(Du hast zwei Beiträge geschrieben, als ich nur einen geschrieben habe, @stevengj : smile :)

Wenn wir lieber verschachtelte Arrays als Konvention für die direkte Summe verwenden (wie wir es jetzt tun) als BlockArray , kann dies natürlich auch nett und konsistent sein! Wir könnten von einigen zusätzlichen Funktionen profitieren, um das Skalarfeld (eine Art rekursiven eltyp) und die effektive abgeflachte Dimensionalität (eine Art rekursive Größe) zu bestimmen, damit wir ein bisschen leichter überlegen können, welche lineare Algebra wir machen.

Um es klar auszudrücken, ich bin mit beiden Ansätzen zufrieden und habe aus dieser Diskussion viel gelernt. Vielen Dank.

@andyferris , nur weil zwei Räume isomorph sind, heißt das nicht, dass sie der "gleiche" Raum sind, und darüber zu streiten, welcher von ihnen "wirklich" der Vektorraum ist (oder ob sie "wirklich" gleich sind), ist ein metaphysischer Sumpf vor dem wir niemals entkommen werden. (Der Fall direkter Summen unendlichdimensionaler Vektorräume zeigt jedoch eine Einschränkung Ihres "abgeflachten" Konzepts einer direkten Summe als "alle Elemente des einen, gefolgt von allen Elementen des anderen".)

Auch hier bin ich mir nicht sicher, worum es dir geht. Schlagen Sie ernsthaft vor, dass eltype(::Vector{Vector{Complex}}) in Julia Complex sollte oder dass length die Summe der Längen zurückgeben sollte? Dass jeder in Julia gezwungen sein sollte, Ihren "abgeflachten" Isomorphismus für Direktsummenräume zu übernehmen? Wenn nicht, muss der Adjunkt rekursiv sein.

Und wenn Sie "nur versuchen, besser zu verstehen, ohne einen bestimmten Punkt zu machen", können Sie es in ein anderes Forum bringen? Dieses Problem ist ohne metaphysische Argumente über die Bedeutung von Direktsummenräumen verwirrend genug.

Nur weil zwei Räume isomorph sind, heißt das nicht, dass sie der "gleiche" Raum sind

Das habe ich definitiv nicht vorgeschlagen.

Auch hier bin ich mir nicht sicher, worum es dir geht

Ich schlage ernsthaft vor, dass, wenn Sie lineare Algebra mit einem AbstractArray möchten, das eltype besser ein Ring ist (Unterstützung + , * und conj ), was zB eltypes von Vector weil [1,2] * [3,4] nicht funktioniert. Und die length eines Vektors würden tatsächlich die Dimensionalität Ihrer linearen Algebra darstellen. Ja, dies schließt unendlich dimensionale Vektorräume aus, aber im Allgemeinen kann in Julia AbstractVector nicht unendlich groß sein. Ich denke, dies würde es einfacher machen, über die Implementierung und Verwendung der linearen Algebra-Funktionalität auf Julia nachzudenken. Benutzer müssten jedoch explizit eine direkte Summe über ein BlockArray oder ähnliches angeben, anstatt verschachtelte Array-Strukturen zu verwenden.

Dies ist ein ziemlich brechender Vorschlag und hat bemerkenswerte Nachteile (einige haben Sie erwähnt), daher werde ich nicht unglücklich sein, wenn wir sagen "das ist eine schöne Idee, aber es wird nicht praktisch sein". Ich habe jedoch auch versucht darauf hinzuweisen, dass der Ansatz mit verschachtelten Arrays einige Dinge über die zugrunde liegende lineare Algebra etwas undurchsichtiger macht.

All dies scheint für das OP direkt relevant zu sein. Mit einem erzwungenen abgeflachten Ansatz würden wir rekursive Transponierte / Adjunkte weglassen, und wenn wir rekursive Transponierte / Adjunkte weglassen würden, könnten nur abgeflachte Strukturen für die lineare Algebra lebensfähig sein. Wenn wir keinen abgeflachten Ansatz erzwingen, müssen wir einen rekursiven Adjunkt beibehalten. Für mich schien es eine zusammenhängende Entscheidung zu sein.

Schlagen Sie ernsthaft vor, dass eltype(::Vector{Vector{Complex}}) in Julia Complex sollte oder dass length die Summe der Längen zurückgeben sollte?

Nein, tut mir leid, vielleicht war das, was ich dort geschrieben habe, nicht klar - ich schlug lediglich vor, dass zwei neue Komfortfunktionen für diesen Zweck unter bestimmten Umständen nützlich sein könnten. Manchmal möchten Sie vielleicht zum Beispiel die zero oder one dieses Rings.

Wollen Sie damit sagen, dass jeder in Julia gezwungen sein sollte, Ihren "abgeflachten" Isomorphismus für Direktsummenräume zu übernehmen?

Ich war darauf hindeutet , dass als eine Möglichkeit, ja. Ich bin nicht zu 100% davon überzeugt, dass dies die beste Idee ist, aber ich denke, dass es einige Vor- und Nachteile gibt.

Wenn wir hier den Bereich der umsetzbaren Änderungen zurückgeben, scheint es, dass die vereinfachte Version meines Vorschlags von richtige Weg ist, um hierher zu gelangen, dh:

  • Behalten Sie conj unverändert (elementweise).
  • Stellen Sie sicher, dass a' adjoint(a) , und machen Sie es immer rekursiv (und schlagen Sie daher für Arrays von Zeichenfolgen usw. fehl, bei denen für die Elemente kein Adjoint definiert ist).
  • Stellen Sie sicher, dass a.' transpose(a) , und machen Sie es niemals rekursiv (nur permutedims ), und ignorieren Sie daher den Typ der Elemente.

Die wichtigsten tatsächlichen "Aufgaben", die daraus extrahiert werden können, sind:

  1. [] Benennen Sie ctranspose in adjoint .
  2. [] Ändern Sie transpose , dass es nicht rekursiv ist.
  3. [] Finde heraus, wie dies zu Zeilenvektoren passt.

Ich vermute, dass das Verlassen auf rekursive Transponierung so selten ist, dass wir dies einfach in 1.0 ändern und als NEWS-Unterbrechung auflisten können. Das Ändern von ctranspose in adjoint kann zu einer normalen Abwertung führen (dies geschieht jedoch für 1.0). Gibt es noch etwas zu tun?

@StefanKarpinski , wir müssen auch die entsprechende Änderung an RowVector vornehmen. Eine Möglichkeit wäre, RowVector in Transpose und Adjoint für faulen nicht rekursiven transpose bzw. faulen rekursiven adjoint aufzuteilen und um Conj loszuwerden (faul conj ).

Noch ein paar zumindest tangential verwandte Änderungen

  • Einige Ansichtstypen für transpose und adjoint von AbstractMatrix
  • Machen Sie adjoint(::Diagonal) rekursiv (wahrscheinlich sollten wir diesen "Fehler" in Diagonal für transpose und ctranspose vor Julia v0.6.0 beheben)
  • Verwenden Sie den richtigen "Skalartyp" in folgenden Dingen: v = Vector{Vector{Float64}}(); dot(v,v) (derzeit ein Fehler - es wird versucht, zero(Vector{Float64}) )

OK, ich habe versucht, rekursive Blockmatrizen ernster zu nehmen, und um dies zu belegen, versuche ich auch, das Verhalten von Diagonal hier zu verbessern (es gibt bereits einige Methoden, die eine Blockdiagonale vorwegnehmen Struktur, wie z. B. getindex , daher erscheint es in diesem Fall natürlich, die Transponierung rekursiv zu machen.

Wo ich stecken bleibe und was alle meine obigen Diskussionspunkte direkt motiviert hat, ist, wie man lineare Algebraoperationen an einer solchen Struktur durchführt, einschließlich inv , det , expm , eig und so weiter. Zum Beispiel gibt es eine Methode für det(::Diagonal{T}) where T die nur das Produkt aller diagonalen Elemente verwendet. Funktioniert hervorragend für T <: Number und funktioniert auch, wenn alle Elemente quadratische Matrizen gleicher Größe sind . (Wenn es sich um quadratische Matrizen gleicher Größe handelt, bilden die Elemente natürlich einen Ring, und das ist alles sehr sinnvoll - auch eine rekursive Transponierung (edit: adjoint) ist das Richtige.)

Wenn wir jedoch an eine allgemeine Blockdiagonalmatrix Diagonal{Matrix{T}} oder eine Blockmatrix Matrix{Matrix{T}} denken, können wir allgemein von ihrer Determinante als Skalar T sprechen. Dh wenn die Größe (3 ⊕ 4) × (3 ⊕ 4) (eine 2 × 2-Matrix mit diagonalen Elementen, die Matrizen der Dimensionen 3 × 3, 4 × 4 sind und mit nicht diagonalen Elementen übereinstimmen), sollte det das zurückgeben Determinante der "abgeflachten" 7 × 7-Struktur, oder sollte es einfach versuchen, die 2 × 2-Elemente so zu multiplizieren, wie sie sind (und in diesem Fall fehlerfrei)?

(Bearbeiten: Ich habe die obigen Abmessungen so geändert, dass sie alle unterschiedlich sind, wodurch eine mehrdeutige Sprache vermieden werden sollte.)

Ich habe kein Problem damit, dass a' rekursiv ist, aber ich persönlich würde die neue Notation a.' äußerst verwirrend finden. Wenn Sie mir die Syntax a.' würden, würde ich Ihnen basierend auf meinem mentalen Modell von Julia sagen, dass sie transpose.(a) ausführt, dh elementweise Transponieren von a , und ich wäre vollständig falsch. Wenn Sie mir alternativ zeigen würden, dass a' und a.' beide Optionen für eine Transponierung sind und dass eine davon auch elementweise rekursiv ist, würde ich Ihnen sagen, dass a.' die haben muss elementweise Rekursion, und ich würde mich wieder irren.

Natürlich ist mein mentales Modell dessen, was . bedeutet, in diesem Fall einfach falsch. Aber ich vermute, ich bin nicht der einzige, der diese Syntax genau falsch interpretiert. Zu diesem Zweck würde ich vorschlagen, etwas anderes als .' für die nicht rekursive Transponierung zu verwenden.

@rdeits Ich fürchte, diese Syntax leitet sich von MATLAB ab. Dies wurde ein wenig unglücklich, als die (ganz wunderbare) Dot-Call-Broadcast-Syntax für v0.5 eingeführt wurde.

Wir könnten unseren eigenen Weg getrennt von MATLAB beschreiten und dies ändern - aber ich glaube, es gab irgendwo eine separate Diskussion darüber (erinnert sich jemand, wo?).

Ah, das ist unglücklich. Vielen Dank!

Noch eine Aufgabe:

  • [] Korrigieren Sie issymmetric und ishermitian , dass sie mit transpose und adjoint übereinstimmen. Ersteres von jedem Paar ist nicht rekursiv, letzteres von jedem Paar ist rekursiv.

Die Matlab .' -Syntax ist im Kontext unserer neuen . -Syntax definitiv ziemlich unglücklich. Ich wäre nicht dagegen, dies zu ändern, aber dann brauchen wir eine neue Syntax für die Transponierung, und ich bin nicht sicher, ob wir eine zur Verfügung haben. Hat jemand Vorschläge zur Umsetzung?

Verschieben wir die Diskussion über die Transponierungssyntax hierher: https://github.com/JuliaLang/julia/issues/21037.

Ich habe keine feste Meinung darüber, dass transpose / ctranspose / adjoint rekursiv ist, aber ich würde A::Matrix{Matrix{T}} lieber nicht wie eine Blockmatrix in der behandeln Sinn für ein faules hvcat , was @andyferris zumindest teilweise zu implizieren scheint. Das heißt, wenn die Elemente von A alle quadratische Matrizen derselben Größe wären (dh einen Ring bilden), würde ich erwarten, dass det(A) wieder ein Quadrat dieser Größe zurückgibt. Wenn sie rechteckig oder unterschiedlich groß sind, würde ich einen Fehler erwarten.

Das heißt, ein Blockmatrix- / Lazy-Cat-Typ könnte nützlich sein, aber er sollte den ganzen Weg gehen und auch zB getindex , um an den abgeflachten Daten zu arbeiten. Aber ich würde definitiv nicht gerne sehen, dass dieses Konzept von Matrix oder einem anderen der vorhandenen Matrixtypen wie Diagonal absorbiert wird.

Das ist eine Erleichterung, @martinholters. Was ich früher in diesem Thread in Panik versetzt habe, war die Idee, dass wir Julias gesamtes lineares Algebra-Framework irgendwie auf Matrix{Matrix} für beliebige Blockmatrizen anwenden können sollten (wobei die Submatrizen unterschiedliche Größen haben).

Was ich streite für mit dem „Abflachung“ jede Phantasie Selbstbeobachtung der ist nicht das, was die Elemente tun, und behandelt nur die Elemente auf ihren eigenen Verdienst als Elemente eines Ringes. Rekursives ctranspose / adjoint erweitert dies jedoch, um Elemente zuzulassen, die sich wie lineare Operatoren verhalten, was korrekt und nützlich erscheint. (Der andere Fall sind Elemente eines Vektors, die wie ein Vektor wirken, wobei auch ein rekursiver Adjunkt korrekt ist).

Um noch ein bisschen weiter zurückzugehen - was war die ursprüngliche Motivation, das standardmäßige No-Op-Verhalten für transpose(x) = x und ctranpsose(x) = conj(x) zu entfernen? Diese schienen mir immer sehr nützlich zu sein.

Um noch etwas weiter zurückzugehen - was war die ursprüngliche Motivation, das Standard-No-Op-Verhalten für Transponierung (x) = x und Ctranpsose (x) = Konj (x) zu entfernen? Diese schienen mir immer sehr nützlich zu sein.

Dies wurde durch benutzerdefinierte Linearoperatortypen (die keine Untertypen von AbstractArray sein können) motiviert, die sich nicht auf ctranspose . Dies bedeutete, dass sie das falsche No-Op-Verhalten von einem Fallback geerbt hatten. Wir haben versucht, den Versand so zu strukturieren, dass Fallbacks niemals stillschweigend falsch sind (aber möglicherweise eine pessimistische Komplexität aufweisen). https://github.com/JuliaLang/julia/issues/13171

Wir scheinen zwei Optionen zu haben - in beiden Fällen wird transpose nicht rekursiv, andernfalls:

  1. Nicht rekursiv ctranspose .
  2. Rekursiv ctranspose .

@stevengj , @jiahao , @andreasnoack - was sind deine Vorlieben hier? Andere?

Ich habe viel darüber nachgedacht , da @jiahao ‚s JuliaCon 2017 reden.

Ich bin immer noch der Meinung, dass lineare Algebra in Bezug auf ein "skalares" Feld als Elementtyp T . Wenn T ein Feld ist (unterstützt + , * und conj (auch - , / , ..). .)) dann verstehe ich nicht, warum Methoden in Base.LinAlg fehlschlagen sollten.

OTOH Es ist durchaus üblich (und gültig), beispielsweise über die Transponierung einer Blockmatrix zu sprechen. Ich denke, wir können hier aus der "mathematischen" Typentheorie lernen, die zum Beispiel versucht, mit seltsamen Aussagen umzugehen, die sich aus der Erörterung von Mengen von Mengen ergeben, indem sie Skalarsätze erster Ordnung, Skalarsätze zweiter Ordnung und Dritte haben order "Sätze von Sätzen von Sätzen von Skalaren und so weiter. Ich denke, wir haben hier das gleiche Problem (und die gleiche Chance), wenn wir uns mit Arrays von Arrays befassen - wir können möglicherweise Julias Typsystem verwenden, um Arrays "erster Ordnung" von "echten" Skalaren, "Arrays zweiter Ordnung" von Arrays von Skalaren usw. Zu beschreiben Ich denke, die langen Diskussionen zwischen @stevengj , mir und anderen resultierten aus der Tatsache, dass Sie, ja, eine selbstkonsistente Reihe von Operationen für Arrays beliebiger "Ordnung" und in diesem Rahmen (c)

Für mich ist der entscheidende Punkt hier, dass Sie Ihren "Skalaren" einige Operationen beibringen müssen, die normalerweise nur für Vektoren und Matrizen definiert sind, damit das Array "beliebiger Reihenfolge" funktioniert. Die aktuelle Julia-Methode, die dies tut, ist transpose(x::Number) = x . Ich würde es jedoch wirklich vorziehen, wenn wir unsere semantische Unterscheidung zwischen Arrays und Skalaren durch Entfernen dieser Methode erweitern würden - und ich denke, dass dies durchaus machbar ist.

Der nächste Schritt wäre, Base.LinAlg den Unterschied zwischen einer Matrix erster Ordnung und einer Matrix zweiter Ordnung usw. beizubringen. Ich habe über zwei realisierbare Optionen nachgedacht, es kann noch mehr geben, ich weiß es wirklich nicht:

  • Haben Sie eine Art Opt-In-Eigenschaft für "Arrayness", so dass transpose(::AbstractMatrix{T}) rekursiv ist, wenn T ein AbstractMatOrVec ist und nicht, wenn T ist Number , und machen Sie es ansonsten irgendwie konfigurierbar (Opt-In, Opt-Out, was auch immer). (In gewisser Weise wurde und wird dies derzeit durch die Steuerung des Verhaltens der transpose -Methode für die Elemente erreicht, aber ich denke, es ist sauberer, das Verhalten der transpose -Methode der (äußeren) zu steuern ) Array).
  • Alternativ können Sie behaupten, dass die meisten AbstractArray s, einschließlich Array erster Ordnung sind (ihre Elemente sind Skalarfelder), und einen anderen AbstractArray -Typ verwenden (z. B. NestedArray ) um Arrays zu verpacken und zu "markieren", dass ihre Elemente als Arrays und nicht als Skalare behandelt werden sollen. Ich habe versucht, ein wenig damit herumzuspielen, aber es schien, dass die oben genannte Option für die Benutzer wahrscheinlich weniger belastend ist.

Ich habe das Gefühl, dass Julia eine starke semantische Trennung zwischen Arrays und Skalaren vornimmt und dass die aktuelle Situation (für mich) ein wenig unvereinbar damit ist, da Skalare über die Array-Eigenschaft transpose "unterrichtet" werden mussten. Ein Benutzer kann klar erkennen, dass Matrix{Float64} eine Matrix von Skalaren (Array erster Ordnung) und Matrix{Matrix{Float64}} eine Blockmatrix (Array zweiter Ordnung) ist. Es kann für uns konsistenter sein, das Typsystem oder die Merkmale zu verwenden, um zu bestimmen, ob ein Array "erster Ordnung" ist oder was auch immer. Ich denke auch, dass die erste Option, die ich aufgelistet habe, Julia benutzerfreundlicher machen würde (damit ich ["abc", "def"].' ), aber die Flexibilität behalten würde, eine vernünftige / nützliche Standardeinstellung mit Blockmatrizen zu machen.

Es tut mir leid, dass ich so lange Harfe spielen musste, aber nachdem @jiahao gesprochen hatte, hatte ich das Gefühl, dass wir hier etwas besser abschneiden könnten. Ich bin sicher, dass wir die anderen von @StefanKarpinski oben genannten Optionen "funktionieren" lassen können, aber für mich würde es "funktionieren", genau wie die Matrix- / Vektoralgebra vor der Einführung von RowVector "funktioniert".

Die TLDR war - nicht machen ( c ) transpose entweder rekursiv oder nicht - lassen Sie es beides tun (dh konfigurierbar sein).

Das sind gute Punkte, aber ich möchte nur darauf hinweisen, dass fast alle (wenn nicht alle) Beschwerden über die Rekursivität von (c)transpose mit der nicht-mathematischen Verwendung von ' und .' beim Umformen zB Vector{String} und Vector{PyObject} in 1xn-Matrizen. Es ist leicht, Typ für Typ zu reparieren, aber ich kann sehen, dass es ärgerlich ist. Der Gedanke war jedoch, dass die rekursive Definition die mathematisch korrekte war und dass "richtig, aber in vielen Fällen ärgerlich" besser war als "praktisch, aber in seltenen Fällen falsch".

Eine mögliche Lösung könnte Ihr Vorschlag im ersten Aufzählungszeichen sein, dh nur rekursive Transponierungen für Array-ähnliche Elemente. Ich glaube, T<:AbstractVecOrMat würde die meisten Fälle abdecken, in denen der Status auf "praktisch, aber in sehr seltenen Fällen falsch" geändert wird. Der Grund, warum es immer noch falsch sein könnte, ist, dass einige Operator-ähnliche Typen nicht in die Kategorie AbstractMatrix passen würden, da es AbstractArray Schnittstelle getindex ) geht , keine lineare Algebra.

@andyferris , der Adjunkt und das Dual eines Skalars sind perfekt definiert, und ctranspose(x::Number) = conj(x) ist korrekt.

Mein Gefühl ist, dass die meisten Verwendungen von transpose "nicht mathematisch" sind und die meisten Verwendungen von ctranspose (dh Verwendungen, bei denen das Konjugationsverhalten gewünscht wird) mathematisch sind (da es keinen anderen Grund zur Konjugation gibt ). Daher würde ich eher nicht rekursive transpose und rekursive ctranspose bevorzugen.

Persönlich denke ich, dass der Versuch, Block-Arrays als verschachtelte Arrays zu betrachten, aus den hier genannten Gründen kompliziert wird und es vielleicht besser ist, einen dedizierten Typ à la https://github.com/KristofferC/BlockArrays.jl für zu haben diese.

Sieht gut aus, @KristofferC.

Es gibt einen sehr gültigen Punkt, den @stevengj oben AbstractArray s. Wir brauchen definitiv eine Möglichkeit, rekursive (c) Transponierungen für diese durchzuführen - nicht sicher, ob Sie darüber nachgedacht haben oder nicht, aber ich dachte, ich würde es erwähnen.

Höhepunkte eines lockeren Gesprächs zu diesem Thema. Fasst einige der oben genannten Themen zusammen. Konzentriert sich auf Semantik, vermeidet Rechtschreibung. (Danke an alle, die an diesem Gespräch teilgenommen haben! :))

Es gibt drei semantisch unterschiedliche Operationen:
1) "mathematischer Adjunkt" (rekursiv und faul)
2) "mathematische Transponierung" (idealerweise rekursiv und faul)
3) "strukturelle Transponierung" (idealerweise nicht rekursiv und? Eifrig?)

Gegenwärtige Situation: "mathematischer Zusatz" wird ctranspose , "mathematische Transponierung" transpose und "strukturelle Transponierung" permutedims(C, (2, 1)) für zweidimensionale C und reshape(C, 1, length(C)) für eindimensionale C . Das Problem: "Strukturelle Transponierung" ist eine häufige Operation, und die permutedims / reshape Geschichte ist in der Praxis etwas verwirrend / unnatürlich / nervig.

Wie das entstand: Zuvor wurde "strukturelle Transponierung" mit "mathematischem Adjunkt" / "mathematischer Transponierung" über generische No-Op-Fallbacks wie transpose(x::Any) = x , ctranspose(x::Any) = conj(x) und conj(x::Any) = x . Diese Fallbacks führten dazu, dass [c]transpose und die zugehörigen Postfix-Operatoren ' / .' in den meisten Fällen für die "strukturelle Transponierung" dienen. Großartig. Aber sie haben auch Operationen mit [c]transpose für einige benutzerdefinierte numerische Typen fehlgeschlagen (falsche Ergebnisse zurückgeben), ohne die Definition von [c]transpose -Spezialisierungen für diese Typen. Autsch. Daher wurden diese generischen No-Op-Fallbacks entfernt, was die gegenwärtige Situation ergab.

Die Frage ist, was jetzt zu tun ist.

Ideales Ergebnis: Stellen Sie eine einheitliche, natürliche und kompakte Beschwörung für "strukturelle Transponierung" bereit. Unterstützen Sie gleichzeitig semantisch korrekte mathematische Adjunkte und Transponierte. Vermeiden Sie die Einführung kniffliger Eckfälle bei der Verwendung oder Implementierung.

Es gibt zwei allgemeine Vorschläge:

(1) Stellen Sie mathematische adjungierte, mathematische Transponierte und strukturelle Transponierte als drei syntaktisch und semantisch unterschiedliche Operationen bereit. Vorteile: Lässt alles funktionieren, trennt Konzepte sauber und vermeidet knifflige Eckfälle. Nachteile: Drei Operationen zum Erklären und Implementieren.

(2) Schuhhorn die drei Operationen in zwei. Es gibt drei Formen dieses Vorschlags:

(2a) Machen Sie transpose semantisch "strukturelle Transponierte", die in häufigen Fällen auch als "mathematische Transponierte" dient. Vorteile: Zwei Operationen. Funktioniert wie erwartet für Container mit eindeutig skalaren Elementen. Nachteile: Jeder, der eine "mathematische Transponierungs" -Semantik erwartet, erhält stillschweigend falsche Ergebnisse für Objekte, die komplexer sind als Container mit eindeutig skalaren Elementen. Wenn der Benutzer dieses Problem erkennt, müssen zum Erreichen einer "mathematischen Transponierungs" -Semantik neue Typen und / oder Methoden definiert werden.

(2b) Führen Sie ein Merkmal ein, das die "Mathematik" eines Typs angibt. Wenn beim Anwenden von adjoint / transponieren auf ein Objekt die Container- / Elementtypen "mathy" sind, rekursieren Sie; Wenn nicht, nicht rekursiv. Vorteile: Zwei Operationen. Könnte eine Vielzahl von häufigen Fällen abdecken. Nachteile: Leidet unter demselben Problem wie die generischen No-Op-Fallbacks [c]transpose , dh es kann stillschweigend zu falschen Ergebnissen für benutzerdefinierte numerische Typen führen, denen die erforderlichen Merkmalsdefinitionen fehlen. Ermöglicht nicht die Anwendung der strukturellen Transponierung auf einen "mathematischen" Typ oder der mathematischen Transponierung auf einen "nicht mathematischen" Typ. Es ist nicht klar, wie in allen Fällen entschieden werden soll, ob ein Objekt "mathematisch" ist. (Was ist beispielsweise, wenn der Elementtyp abstrakt ist? Ist die rekursive / nicht rekursive Entscheidung dann Laufzeit und elementweise oder Laufzeit und erfordert einen ersten Durchlauf über alle Elemente im Container, um ihren kollektiven Typ zu überprüfen?)

(2c) Behalten Sie adjoint ( ctranspose ) "mathematischer Adjunkt" und transpose "mathematische Transponierung" bei und führen Sie spezielle (nicht generische Fallback-) Methoden für adjoint / transpose für nicht numerische Skalartypen (z. B. adjoint(s::AbstractString) = s ). Vorteile: Zwei Operationen. Korrekte mathematische Semantik. Nachteile: Erfordert eine stückweise Definition von adjoint / transpose für nicht numerische Typen, was die generische Programmierung behindert. Ermöglicht keine Anwendung der strukturellen Transponierung auf "mathematische" Typen.

Die Vorschläge (1) und (2a) fanden eine wesentlich breitere Unterstützung als (2b) und (2c).

Weitere Informationen finden Sie in # 19344, # 21037, # 13171 und slack / # linalg. Beste!

Danke für das nette Schreiben!

Ich möchte eine weitere Möglichkeit auf den Tisch legen, die gut zu Option 1 passt: Verschiedene Containertypen für mathematische Matrizen und Tabellendaten. Dann könnte die Bedeutung von A' durch den Typ von A (Hinweis: nicht der oben beschriebene Elementtyp). Nachteile hier sind, dass dies eine Menge von convert s zwischen den beiden erfordern könnte (oder?) Und natürlich sehr störend wäre. Zugegeben, ich bin mehr als skeptisch, ob die Vorteile diese Störung rechtfertigen würden, wollte sie aber trotzdem erwähnen.

Danke, ausgezeichnetes Schreiben. Ich stimme für (1).

Tolles Schreiben. Beachten Sie, dass für (1) die Schreibweisen sein können:

  • Adjoint: a' (rekursiv, faul)
  • Mathematische Transponierung: conj(a') (rekursiv, faul)
  • Strukturelle Transponierung: a.' (nicht rekursiv, eifrig)

Es müsste also nicht unbedingt ein neuer Operator eingeführt werden - die mathematische Transponierung wäre nur eine (faule und daher effiziente) Zusammensetzung von Adjunkt und Konjugation. Das größte Problem ist, dass zwei ähnlich aussehende Operatoren, dh Postfix ' und .' , semantisch ziemlich unterschiedlich sind. Ich würde jedoch davon ausgehen, dass im korrektesten generischen Code, ob jemand ' oder .' hat, ein zu 99% genauer Indikator dafür ist, ob er "mir den Zusatz dieser Sache geben" oder "tauschen" meinte die Dimensionen dieser Sache ". In Fällen, in denen jemand ' und tatsächlich "die Dimensionen dieses Dings tauschen" meinte, wäre sein Code bereits für jede Matrix mit Elementen falsch, deren skalarer Zusatz nicht trivial ist, z. B. komplexe Zahlen. In den wenigen verbleibenden Fällen, in denen jemand tatsächlich "gib mir das Konjugat des Adjunkts davon" meinte, würde ich argumentieren, dass das Schreiben von conj(a') diese Bedeutung viel klarer macht, da in der Praxis die Leute tatsächlich a.' bedeuten "Tauschen Sie die Dimensionen von a ".

Die Ermittlung aller zugrunde liegenden generischen Typen, die wir dafür benötigen würden, muss noch ermittelt werden, aber @andyferris und @andreasnoack haben bereits einige Gedanken zu diesem Thema und es scheint möglich.

Ich dachte, ich sollte auch aktualisieren, da diese Diskussion an anderer Stelle weiterging. Ich gebe zu, dass es eine (offensichtliche?) Sache an diesem Vorschlag gab, die ich völlig übersehen hatte, nämlich dass ich davon ausgegangen bin, dass wir weiterhin .' für die lineare Algebra verwenden würden, aber ich hätte erkennen müssen, dass dies nicht der Fall ist !

Zum Beispiel muss vector.' kein RowVector (da RowVector ein lineares Algebra-Konzept ist, unser erster Versuch eines "dualen" Vektors) - es kann einfach sei ein Matrix . Wenn ich die "nicht konjugierende Transponierte" in der linearen Algebra haben möchte, nehme ich wirklich conj(adjoint(a)) , und das verwenden wir und empfehlen allen Benutzern der linearen Algebra (bis jetzt habe ich) Ich hatte seit MATLAB eine langjährige "schlechte" Angewohnheit, nur a.' anstelle von a' für die Transponierung einer Matrix (oder eines Vektors) zu verwenden, von der ich wusste, dass sie real ist, wenn ich wirklich das adjoint (oder dual) - die Namensänderung wird hier eine große Hilfe sein).

Ich werde auch kurz darauf hinweisen, dass dies einen interessanten Raum offen lässt. Zuvor mussten vector' und vector.' die doppelten Anforderungen erfüllen, eine "Datentransponierung" durchzuführen und so etwas wie den doppelten Vektor zu nehmen. Jetzt, da .' für die Manipulation von Array-Größen und ' ein lineares Algebra-Konzept ist, können wir möglicherweise RowVector in 1D DualVector oder etwas anderes ändern vollständig. (Vielleicht sollten wir das hier nicht diskutieren - wenn jemand Appetit darauf hat, machen wir ein separates Thema.)

Zum Schluss kopiere ich einen vorgeschlagenen Aktionsplan von Slack:

1) Verschiebe transpose von LinAlg auf Base und füge Depwarns (nur 0.7) hinzu, die keine RowVector mehr machen oder rekursiv sind (wenn das möglich ist).
2) Benennen Sie ctranspose adjoint überall in
3) Stellen Sie sicher, dass Vector , ConjVector und RowVector mit Kombinationen von ' und conj und * funktionieren Benennen Sie RowVector . (hier machen wir auch conj(vector) faul).
4) Führen Sie einen Lazy Matrix Adjoint ein, der auch gut mit conj und ConjMatrix interagiert
5) Entfernen Sie A_mul_Bc usw. Benennen Sie A_mul_B! in mul! (oder *! ).
6) Gewinn

@stevengj schrieb:

@andyferris , der Adjunkt und das Dual eines Skalars sind perfekt definiert, und ctranspose(x::Number) = conj(x) ist korrekt.

Für die Aufzeichnung stimme ich dem definitiv zu.

Ich habe das Gefühl, dass die meisten Transponierungsverwendungen "nicht mathematisch" sind und die meisten Transponierungsverwendungen (...) mathematisch sind

Die Idee wird also sein, dies zu formalisieren: Alle Verwendungen von transpose werden "nicht mathematisch" und die einzige Verwendung von adjoint wird "mathematisch" sein.

Zum Beispiel muss vector.' kein RowVector (da RowVector ein lineares Algebra-Konzept ist, unser erster Versuch eines "dualen" Vektors) - es kann einfach sei ein Matrix .

Wir wollen jedoch immer noch, dass .' faul ist. zB X .= f.(x, y.') sollte immer noch nicht zugeordnet sein.

Hier ist eine Frage: Wie sollen wir issymmetric(::AbstractMatrix{<:AbstractMatrix}) implementieren? Wäre es eine nicht rekursive Prüfung, um mit transpose übereinzustimmen? Ich schaue auf die Implementierung; Es wird davon ausgegangen, dass die Elemente transpose . OTOH scheint es durchaus gültig zu sein zu prüfen, ob issymmetric(::Matrix{String}) ...

Es ist derzeit nicht rekursiv, wenn es in Symmetric btw eingeschlossen ist

julia> A = [rand(2, 2) for i in 1:2, j in 1:2]; A[1, 2] = A[2, 1]; As = Symmetric(A);

julia> issymmetric(A)
false

julia> issymmetric(As)
true

julia> A == As
true

Ja, ich arbeite daran, eine PR dafür zu erstellen, und es gibt viele solcher Inkonsistenzen. (Ich bin vor kurzem darauf gestoßen, als ich nach jeder Instanz von transpose , adjoint und conj im Array-Code gesucht habe).

Sofern nicht anders angegeben, implementiere ich ein Verhalten wie issymmetric(a) == (a == a.') und ishermitian(a) == (a == a') . Diese scheinen an sich ziemlich intuitiv zu sein, und die vorhandene Verwendung von issymmetric gilt für Number Elementtypen (oder hat häufig andere Fehler / Annahmen, die für verschachtelte Arrays nicht viel Sinn machen) .

(Die alternative Implementierung ist issymmetric(a) == (a == conj(adjoint(a))) ... nicht so "hübsch" und funktioniert auch nicht für "Daten" -Arrays (von String usw.), aber sie fällt bei Arrays von Number )

Sofern nicht anders angegeben, implementiere ich ein Verhalten wie issymmetric(a) == (a == a.') und ishermitian(a) == (a == a')

Scheint mir die richtige Lösung zu sein.

Update: Meine Meinung geändert. Symmetrisch ist wahrscheinlich hauptsächlich für die lineare Algebra

Nur ein kleiner Vorschlag: Es ist oft nützlich, eine Summe über das Produkt zweier Vektoren ( dotu ) zu machen, und die Rückgabe eines Skalars durch x.’y ist eine bequeme Möglichkeit, dies zu tun. Ich würde es also befürworten, kein Matrix von transpose(::Vector)

Ja, es wird ein RowVector also sollten Sie einen Skalar erhalten. (Beachten Sie, dass die Transponierung nicht rekursiv ist.)

Entschuldigen Sie den möglicherweise tangentialen Kommentar, aber viele Leute in diesem Thread sprechen immer wieder von einem "Skalarring des Vektorraums". Per Definition müssen die Skalare eines Vektorraums ein Feld bilden, nicht irgendeinen Ring. Eine algebraische Struktur, die einem Vektorraum sehr ähnlich ist, deren Skalare jedoch nur einen Ring und kein Feld bilden, wird als "Modul" bezeichnet. Ich denke jedoch, dass Module etwas zu esoterisch sind, um in der Basis behandelt zu werden. .. Modul.

Da wir Integer-Arrays unterstützen, unterstützen wir effektiv Module, nicht nur Vektorräume. Natürlich können wir auch ganzzahlige Arrays als verhaltensmäßig in Gleitkomma-Arrays eingebettet betrachten, sodass sie eine teilweise Darstellung eines Vektorraums sind. Wir können aber auch Arrays von modularen Ganzzahlen erstellen (zum Beispiel), und mit einem Nicht-Prim-Modul würden wir mit einem Ring arbeiten, der natürlich in keinem Feld eingebettet ist. Kurz gesagt, wir möchten eigentlich Module im Allgemeinen betrachten und nicht nur Vektorräume, aber ich denke, es gibt keinen signifikanten Unterschied für unsere Zwecke (wir sprechen im Allgemeinen nur über + und * ) also dient "Vektorraum" für unsere Zwecke als bekanntere Abkürzung für "Modul".

Ja, Julia unterstützt (und sollte) sicherlich algebraische Operationen sowohl für allgemeine Module als auch für echte Vektorräume. Aber so wie ich es verstehe, ist die allgemeine Philosophie der Community, dass Funktionen in Base Berücksichtigung "gewöhnlicher 'generischer linearer Algebra-Routinen" entworfen werden sollten - so dass beispielsweise exakte lineare Algebra-Berechnungen auf ganzzahligen Matrizen nicht funktionieren gehört nicht zu Base - also sollten wir bei grundlegenden Entwurfsentscheidungen einfach davon ausgehen, dass die Skalare ein Feld bilden. (Obwohl es, wie Sie sagten, praktisch nicht wirklich wichtig ist.)

Crossposting https://github.com/JuliaLang/julia/pull/23424#issuecomment -346678279

Ich schätze die Bemühungen, # 5332 und # 20978 voranzutreiben, die diese Pull-Anfrage darstellt, sehr und bin mit dem größten Teil des zugehörigen Plans an Bord. Ich würde gerne auch die Terminologieentscheidungen und die nachgelagerten Auswirkungen dieser Pull-Anfrage berücksichtigen und daher regelmäßig versuchen, mich davon zu überzeugen, dass diese Entscheidungen die besten verfügbaren Kompromisse ergeben.

Jedes Mal, wenn ich versuche, mich von dieser Position zu überzeugen, stoße ich auf die gleichen Bedenken. Eine dieser Bedenken ist die erhebliche Komplexität der Implementierung, die diese Auswahl für LinAlg erzwingt: Durch das Zusammenführen von Transponierung und Array-Flip wird LinAlg gezwungen, beide zu handhaben, was erfordert, dass LinAlg eine viel größere Menge von unterstützt Typkombinationen in allgemeinen Operationen.

Betrachten Sie zur Veranschaulichung mul(A, B) wobei A und B nackte, nebeneinander verpackte, transponierte oder mit Array-Flip umwickelte Matrix . Ohne Transponierung und Array-Flip zu verschmelzen, kann A ein Matrix , ein adjoint-verpacktes Matrix oder ein transponiert verpacktes Matrix (und ebenfalls B ). mul(A, B) müssen also neun Typkombinationen unterstützen. Aber das Zusammenführen von Transponierung und Array-Flip, A kann ein Matrix , ein adjungiert verpacktes Matrix , ein transponiert verpacktes Matrix oder ein Array- sein. Flip-Wrapped Matrix (und ebenfalls B ). Jetzt brauchen mul(A, B) Unterstützung für 16 Typkombinationen.

Dieses Problem verschlechtert sich exponentiell mit der Anzahl der Argumente. Zum Beispiel benötigen mul!(C, A, B) ohne Zusammenführung Unterstützung von siebenundzwanzig Typkombinationen, während mul!(C, A, B) mit Zusammenführung Unterstützung von vierundsechzig Typkombinationen benötigen. Und natürlich macht das Hinzufügen von Vector s und Nicht- Matrix Matrix- / Operatortypen zu der Mischung die Sache noch komplizierter.

Dieser Nebeneffekt scheint erwägenswert zu sein, bevor mit dieser Änderung fortgefahren wird. Beste!

In diesem Beitrag werden die jüngsten Diskussionen zu Github, Slack und Triage konsolidiert / überprüft und mögliche Wege nach vorne analysiert. (Der spirituelle Nachfolger von https://github.com/JuliaLang/julia/issues/20978#issuecomment-315902532 wurde geboren, als er darüber nachdachte, wie man zu 1.0 kommt.)

Es geht um drei semantisch unterschiedliche Operationen:

  • adjoint (linear algebraisch, rekursiv, standardmäßig idealerweise faul)
  • transponieren (linear algebraisch, rekursiv, standardmäßig idealerweise faul)
  • Array-Flip (abstrakt-Array-ic, nicht rekursiv, idealerweise "faul" standardmäßig)

Status dieser Operationen auf dem Master

  • Adjoint heißt adjoint / ' (ist aber eifrig, abgesehen von ' -involvierenden Ausdrücken, die speziell auf A[c|t]_(mul|rdiv|ldiv)_B[c|t][!] -Anrufe reduziert wurden, um eifrige Zwischenpunkte / Transponierungen zu vermeiden). .

  • Die Transponierung heißt transpose / .' (mit der gleichen Einschränkung wie adjoint ).

  • Array-Flip heißt permutedims(C, (2, 1)) für zweidimensionale C und reshape(C, 1, length(C)) für eindimensionale C .

Die relevanten Themen

  1. 5332: Die spezielle Absenkung auf A[c|t]_(mul|rdiv|ldiv)_B[c|t] und die damit verbundene kombinatorische Sammlung von Methodennamen sollten um 1,0 verschwinden. Das Entfernen dieser speziellen Absenkung / der zugehörigen Methodennamen erfordert ein verzögertes Adjunktieren und Transponieren.

  2. 13171: Adjoint (geb. ctranspose ) und Transponierung wurden früher mit Array-Flip über generische No-Op-Fallbacks wie transpose(x::Any) = x , ctranspose(x::Any) = conj(x) und conj(x::Any) = x . Diese Fallbacks führten dazu, dass Operationen mit [c]transpose für einige benutzerdefinierte numerische Typen stillschweigend fehlschlagen (falsche Ergebnisse zurückgeben), ohne dass [c]transpose Spezialisierungen für diese Typen vorliegen. Die stillschweigende Rückgabe falscher Ergebnisse ist eine schlechte Nachricht, daher wurden diese Fallbacks entfernt (# 17075). Es wäre großartig, keine stilleren Fehler einzuführen.

  3. 17075, # 17374, # 19205: Das Entfernen der vorhergehenden Fallbacks machte das Umdrehen von Arrays weniger bequem. Und zusammen mit (Entschuldigung, meine Schuld) weniger als großen damit verbundenen Abwertungswarnungen verursachte diese Entfernung (vorübergehend?) Beschwerden. Eine bequemere Beschwörung für Array-Flip wäre schön.

  4. 21037: Das Entfernen der jetzt verwirrenden .' -Syntax für 1.0 wäre sehr schön. Die oben erwähnte spezielle Absenkung muss entfernt werden, um diese Änderung zu ermöglichen.

Entwurfsziele zur Lösung dieser Probleme

Entfernen Sie die speziellen Absenkungs- und zugehörigen Methodennamen, vermeiden Sie stille Fehler, stellen Sie intuitive und bequeme Beschwörungsformeln für Transponierung und Array-Flip bereit und entfernen Sie .' . Erreichen Sie das Vorhergehende mit minimaler zusätzlicher Komplexität und Bruch.

Designvorschläge

Das Feld wurde auf zwei Entwurfsvorschläge reduziert:

  1. Rufen Sie adjoint adjoint , transponieren Sie conjadjoint ("konjugiertes adjoint") und Array-Flip transpose . adjoint und conjadjoint leben in LinAlg und transpose leben in Base .

  2. Rufen Sie adjoint adjoint , transponieren Sie transpose und drehen Sie flip . adjoint und transpose leben in LinAlg und flip leben in Base .

Auf den ersten Blick scheinen diese Vorschläge nur oberflächlich anders zu sein. Eine weitere Untersuchung zeigt jedoch tiefe praktische Unterschiede. Lassen Sie uns einen Blick auf diese tiefen praktischen Unterschiede werfen, ohne die relativen oberflächlichen Vorzüge dieser Namensschemata zu diskutieren.

Unterschiede, Ansicht auf hoher Ebene

  1. Komplexität:

    Vorschlag 1 erzwingt durch Aufrufen von Array-Flip transpose LinAlg , Array-Flip zusätzlich zu Transponieren und Adjunktieren zu verarbeiten. Folglich muss LinAlg den Satz von Typkombinationen, die von allgemeinen Operationen unterstützt werden, erheblich erweitern. https://github.com/JuliaLang/julia/pull/23424#issuecomment -346678279 veranschaulicht diese zusätzliche Komplexität beispielhaft, und die folgende Diskussion bestätigt implizit die Existenz dieser zusätzlichen Komplexität.

    Vorschlag zwei erfordert LinAlg Unterstützung nur transponieren und adjungieren, was LinAlg jetzt tut.

  2. Bruch:

    Vorschlag 1 ändert die grundlegende Semantik bestehender Operationen: transpose wird zum Array-Flip und nicht zur Transponierung, und alle transpose -bezogenen Funktionen müssen sich entsprechend ändern. (Zum Beispiel würden alle Multiplikations- und Links- / Rechtsdivisionsoperationen in LinAlg die mit dem Namen transpose LinAlg verknüpft sind, eine semantische Überarbeitung erfordern.) Abhängig davon, wie diese Änderung realisiert wird, verursacht diese Änderung einen stillen Bruch überall dort, wo auf die gegenwärtige Semantik zurückgegriffen wird (absichtlich oder versehentlich).

    Vorschlag zwei behält die grundlegende Semantik aller vorhandenen Operationen bei.

  3. Kupplung:

    Vorschlag 1 bringt ein lineares algebraisches Konzept (Transponieren) in Base und ein abstraktes Array-Konzept (Array-Flip) in LinAlg , wobei Base und LinAlg stark gekoppelt werden

    Vorschlag zwei trennt Dinge wie abstraktes Array und lineare Algebra sauber voneinander, so dass erstere ausschließlich in der Basis und letztere ausschließlich in LinAlg ohne neue Kopplung leben können.

  4. Lautloses vs. lautes Versagen:

    Vorschlag 1 führt zu einem stillschweigend falschen Ergebnis, wenn man transpose aufruft und eine Transponierungssemantik erwartet (aber stattdessen eine Array-Flip-Semantik erhält).

    Das Analogon unter Vorschlag 2 ruft transpose für ein nicht numerisches Array auf, das eine Array-Flip-Semantik erwartet. In diesem Fall kann transpose einen Fehler auslösen, der den Benutzer hilfreich auf flip verweist.

  5. .' : Das Hauptargument dafür, .' nicht zu verwerfen, ist die Länge von transpose . Vorschlag 1 ersetzt .' durch die Namen transpose und conjadjoint , was diese Situation nicht verbessert. Im Gegensatz dazu enthält Vorschlag zwei die Namen flip und transpose , was diese Situation verbessert.

Unterschiede in den Pfaden zu 1.0

Was bedeutet es, unter jedem Vorschlag auf 1.0 zu kommen? Der Weg zu 1.0 ist unter Vorschlag zwei einfacher. Beginnen wir also dort.

Der Weg zu 1.0 unter Vorschlag zwei

  1. Führen Sie faule adjungierte und transponierte Wrapper-Typen in LinAlg , z. B. Adjoint und Transpose . Führen Sie mul[!] / ldiv[!] / rdiv[!] Methoden ein, die auf diesen Wrapper-Typen versendet werden und den entsprechenden A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] Methodencode enthalten. Implementieren Sie die letzteren Methoden als kleine Kinder der ersteren Methoden.

    Dieser Schritt unterbricht nichts und ermöglicht sofort das Entfernen der speziellen Absenkung und die Abwertung von .' :

  2. Entfernen Sie die spezielle Absenkung, die A[c|t]_{mul|ldiv|rdiv}_B[c|t] ergibt, und senken Sie stattdessen einfach ' / .' auf Adjoint / Transpose ; Früher speziell abgesenkte Ausdrücke, die A[c|t]_{mul|ldiv|rdiv}_B[c|t] Anrufe ergeben, werden stattdessen zu äquivalenten mul / ldiv / rdiv Anrufen. Verwerfen Sie A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] auf die entsprechenden mul[!] / ldiv[!] / rdiv[!] Methoden. Veraltet .' .

    Diese Schritte könnten in 0.7 durchgeführt werden. Sie brechen nur zwei Dinge: (1) Code, der sich auf die spezielle Absenkung stützt, um A[c|t]_{mul|ldiv|rdiv}_B[c|t] Methoden für Nicht- Base / LinAlg Typen zu treffen, wird kaputt gehen. Ein solcher Code gibt explizite MethodError s aus, die angeben, was die neue Senkung ergibt / wohin der fehlerhafte Code migriert werden muss. (2) Code, der sich auf isolierte ' s / .' s stützt, die sich streng eifrig verhalten, wird brechen. Der allgemeine Fehlermodus sollte auch explizit MethodError s sein. Rundum ist der Bruch begrenzt und laut.

    Und das wars für Änderungen, die für 1.0 unbedingt erforderlich sind.

    Zu diesem Zeitpunkt würde Adjoint(A) / Transpose(A) einen faulen Adjunkt und eine Transponierung ergeben, und adjoint(A) / transpose(A) würde einen eifrigen Adjunkt und eine Transponierung ergeben. Die letzteren Namen können auf unbestimmte Zeit bleiben oder, falls gewünscht, auf eine andere Schreibweise in 0,7 verworfen werden, z. B. eagereval(Adjoint(A)) / eagereval(Transpose(A)) Modulo-Schreibweise von eagereval oder eageradjoint(A) / eagertranspose(A) . Im Fall der Abschreibung könnten adjoint / transpose dann in 1.0 neu verwendet werden (obwohl ich bei Adjoint(A) / Transpose(A) nicht sicher bin, ob dies der Fall ist notwendig).

    Schließlich...

  3. Führen Sie flip und / oder Flip in Base . Als Feature-Ergänzung kann diese Änderung bei Bedarf in 1.x erfolgen.

Der Weg zu 1.0 unter Vorschlag eins

Was ist mit Vorschlag eins? Im Folgenden werden zwei mögliche Pfade beschrieben. Der erste Pfad konsolidiert Änderungen in 0,7, beinhaltet jedoch einen stillen Bruch. Der zweite Pfad vermeidet einen stillen Bruch, beinhaltet jedoch mehr Änderungen von 0,7-> 1,0. Beide Umrisse entwickeln die Codebasis kontinuierlich weiter. Weniger kontinuierliche Äquivalente könnten einige Arbeiten / Abwanderungen konsolidieren / vermeiden, wären jedoch wahrscheinlich herausfordernder und fehleranfälliger. Die laufende Arbeit scheint dem ersten Weg zu folgen.

Erster Weg unter Vorschlag eins (mit stillem Bruch)
  1. Ändern Sie die Semantik von transpose von Transponieren in Array-Flip und notwendigerweise auch die Semantik aller transpose -bezogenen Funktionen. Beispielsweise müssen alle A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] -Methoden, die mit At_... oder mit ..._Bt[!] enden, möglicherweise semantisch überarbeitet werden. Muss auch zB die Definitionen und das Verhalten von Symmetric / issymmetric ändern. Verschieben Sie transpose selbst in Base .

    Diese Änderungen würden stillschweigend und allgemein brechen.

  2. Führen Sie conjadjoint in LinAlg . Dieser Schritt erfordert die Wiederherstellung aller im vorherigen Schritt berührten Methoden, jedoch in ihrer ursprünglichen semantischen Form und jetzt mit verschiedenen Namen, die mit conjadjoint verknüpft sind (z. B. Aca_... und ..._Bca[!] Namen). . Außerdem müssen Methoden für die zusätzlichen Typkombinationen hinzugefügt werden, die gleichzeitig Array-Flip (jetzt transpose ), Transponieren (jetzt conjadjoint ) und Adjoint in LinAlg erfordern (zum Beispiel die ca Varianten unter A[c|t|ca]_{mul|ldiv|rdiv}_B[c|t|ca][!] ).

  3. Führen Sie Lazy Adjoint ein und transponieren Sie (genannt conjadjoint ) in LinAlg , sagen Sie Adjoint und ConjAdjoint . Führen Sie einen faulen Array-Flip-Wrapper-Typ ( transpose ) in Base , sagen Sie Transpose . Führen Sie mul[!] / ldiv[!] / rdiv[!] Methoden ein, die auf diesen Wrapper-Typen versendet werden und den entsprechenden A[c|t|ca]_{mul|ldiv|rdiv}_B[c|t|ca][!] Methodencode enthalten. Implementieren Sie die letzteren Methoden als kleine Kinder der ersteren Methoden.

  4. Entfernen Sie die spezielle Absenkung, die A[c|t]_{mul|ldiv|rdiv}_B[c|t] ergibt, und senken Sie stattdessen einfach ' / .' auf Adjoint / Transpose ; Früher speziell abgesenkte Ausdrücke, die A[c|t]_{mul|ldiv|rdiv}_B[c|t] Aufrufe ergeben, werden stattdessen zu äquivalenten mul / ldiv / rdiv -Anrufen (obwohl daran erinnert wird, dass sich die Semantik stillschweigend geändert hat). Verwerfen Sie A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] auf die entsprechenden mul[!] / ldiv[!] / rdiv[!] Methoden. Entfernen Sie in ähnlicher Weise die kürzlich eingeführten Methoden Aca_... / ...Bca[!] zugunsten von mul[!] / ldiv[!] / rdiv[!] Äquivalenten. Veraltet .' .

Diese Änderungen müssten in 0.7 erfolgen. Die semantischen Änderungen an bestehenden Operationen würden zu einem breiten, stillen Bruch führen. Das spezielle Entfernen der Absenkung würde den gleichen begrenzten, lauten Bruch ergeben, der oben beschrieben wurde.

Zu diesem Zeitpunkt würde Adjoint(A) / Transpose(A) / ConjAdjoint(A) jeweils einen faulen Adjunkt-, Array-Flip- und Transponierungs- und adjoint(A) / transpose(A) / conjadjoint(A) würde jeweils eifriges Adjoint, Array-Flip und Transponieren ergeben. Die letzteren Namen könnten auf unbestimmte Zeit bleiben oder, falls gewünscht, auch in 0,7 (siehe oben) auf eine andere Schreibweise verworfen werden.

Man könnte ConjAdjoint früher in diesem Prozess einführen, um einige Arbeiten / Abwanderungen zu konsolidieren / zu vermeiden, obwohl die Wahrscheinlichkeit groß ist, dass dieser Ansatz herausfordernder und fehleranfälliger wäre.

Zweiter Weg unter Vorschlag eins (Vermeidung von stillem Bruch)
  1. Stellen Sie eifrige conjadjoint in LinAlg . Migrieren Sie alle Funktionen und Methoden, die derzeit mit transpose verknüpft sind (einschließlich z. B. A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] Methoden mit t ), auf conjadjoint und abgeleitete Namen. Machen Sie alle transpose -bezogenen Namen zu kurzen Kindern der neuen conjadjoint -Äquivalente. Verwerfen Sie alle transpose -bezogenen Namen auf conjadjoint Äquivalente.

  2. Führen Sie Lazy Adjoint- und Transponierungs-Wrapper-Typen (genannt conjadjoint ) in LinAlg , z. B. Adjoint und ConjAdjoint . Führen Sie mul[!] / ldiv[!] / rdiv[!] Methoden ein, die auf diesen Wrapper-Typen versendet werden und den entsprechenden A[c|ca]_{mul|ldiv|rdiv}_B[c|ca][!] Methodencode enthalten. Implementieren Sie die letzteren Methoden als kleine Kinder der ersteren Methoden.

  3. Führen Sie einen faulen Array-Flip-Wrapper-Typ in Base , der Transpose (etwas verwirrend, da gleichzeitig transpose conjadjoint mit Transponierungssemantik auf Transpose ) zu Base funktioniert. Fügen Sie dann LinAlg Methoden für alle zusätzlichen Typkombinationen hinzu, die gleichzeitig Array-Flip unterstützen (jetzt Transpose , aber nicht transpose ), transponieren (jetzt conjadjoint und ConjAdjoint ) und Adjoint in LinAlg erfordert (z. B. die mul[!] / rdiv[!] / ldiv[!] Äquivalente von A[c|t|ca]_{mul|ldiv|rdiv}_B[c|t|ca][!] die derzeit nicht existieren).

  4. Entfernen Sie die spezielle Absenkung, die A[c|t]_{mul|ldiv|rdiv}_B[c|t] ergibt, und senken Sie stattdessen einfach ' / .' auf Adjoint / Transpose ; Früher speziell abgesenkte Ausdrücke, die A[c|t]_{mul|ldiv|rdiv}_B[c|t] Anrufe ergeben, werden stattdessen zu äquivalenten mul / ldiv / rdiv Anrufen. Verwerfen Sie A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] auf die entsprechenden mul[!] / ldiv[!] / rdiv[!] Methoden. Veraltet .' .

Die vorhergehenden Änderungen müssten in 0.7 gehen. Kein leiser Bruch, nur der laute Bruch durch die oben beschriebene Sonderabsenkung.

Zu diesem Zeitpunkt würde Adjoint(A) / Transpose(A) / ConjAdjoint(A) jeweils zu einem faulen Adjunkt, Array-Flip und Transponieren führen. adjoint(A) / conjadjoint(A) würde jeweils einen eifrigen Adjunkt ergeben und transponieren. transpose(A) würde auf conjadjoint veraltet sein; transpose kann auf Wunsch in 1.0 verwendet werden. adjoint könnte auf unbestimmte Zeit bleiben oder zugunsten einer anderen Schreibweise in 0.7 veraltet sein. Eine andere Möglichkeit, conjadjoint zu buchstabieren, könnte direkt in 0.7 gehen.

Man könnte die Schritte 1 und 2 etwas konsolidieren und so einige Arbeiten / Abwanderungen vermeiden, obwohl die Wahrscheinlichkeit groß ist, dass der Ansatz herausfordernder und fehleranfälliger ist.

#

Danke fürs Lesen!

Vielen Dank für die tolle Berichterstattung. Ich stimme im Allgemeinen auch Vorschlag 2 zu, mit dem zusätzlichen Grund, dass der konjugierte Adjunkt überhaupt keine Standardterminologie ist, und ich persönlich finde ihn sehr verwirrend (da der Adjunkt manchmal in bestimmten Zweigen der Mathematik als Terminologie für die einfache Transponierung und das zusätzliche Adjektivkonjugat verwendet wird scheint zu implizieren, dass Konjugation stattfindet).

Ich habe versucht, die ausführlichen Diskussionen oben zu lesen, konnte aber nicht erkennen, zu welchem ​​Zeitpunkt entschieden / motiviert wurde, dass ein mathematisches transpose rekursiv sein sollte. Dies schien aus einer privaten Diskussion (irgendwo zwischen dem 14. und 18. Juli) zu folgen, nach der plötzlich eine mathematische und strukturelle Transponierung eingeführt wurde.
@ Sacha0 beschreibt es als

Wie das entstand: Zuvor wurde "strukturelle Transponierte" mit "mathematischem Adjunkt" / "mathematischer Transponierter" verschmolzen.

Ich bin damit einverstanden, dass (strukturelle) Transponierung mit "adjoint" in Konflikt gebracht wurde, aber hier erscheint "mathematische Transponierung" aus dem Nichts? Könnte dieses Konzept der Vollständigkeit halber / in sich geschlossen und warum es rekursiv sein sollte, kurz wiederholt / zusammengefasst werden?

In Bezug darauf denke ich, dass niemand wirklich transpose im abstrakten mathematischen Sinne als Operation auf linearen Karten verwendet , aus dem einfachen Grund, dass die Transponierung einer Matrix ein Objekt wäre, auf das nicht eingewirkt wird Vektoren, aber auf RowVector s, dh es würde RowVector auf RowVector abbilden. Angesichts der fehlenden Argumentation für eine rekursive Transponierung sehe ich wirklich keinen Unterschied zwischen der einfachen Matrixtransponierung, die typischerweise als (basenabhängiges) Konzept definiert wird, und der neu vorgeschlagenen Operation flip .

Danke @ Sacha0 für das tolle Schreiben. Ich bevorzuge Vorschlag 2 (Rufen Sie adjoint adjoint , transponieren Sie transpose und Array-Flip flip . adjoint und transpose leben in LinAlg und flip leben in Base .). Für mich scheint dies das beste Endergebnis zu liefern (was das Hauptanliegen sein sollte) und ermöglicht auch einen saubereren Weg dorthin für v1.0. Ich stimme Ihren obigen Punkten zu, daher werde ich diese nicht wiederholen, aber hier sind einige zusätzliche Kommentare.

Ein Argument für nicht rekursive transpose ist, dass die .' -Syntax sehr praktisch ist und daher beispielsweise für Matrix{String} . Angenommen, die Syntax verschwindet (# 21037) und man muss transpose(A) anstelle von A.' , dann denke ich, dass eine flip -Funktion eine willkommene Ergänzung wäre (kürzer) und klarer als transpose(A) und definitiv schöner als permutedims(A, (2,1)) ).

Die Entkopplung zwischen LinAlg und Base , die Vorschlag 2 bietet, ist ebenfalls sehr schön. Nicht nur, weil LinAlg nur um den Umgang mit Transpose und Adjoint kümmern muss, sondern auch, dass klar ist, dass wir transpose und adjoint berücksichtigen. LinAlg Operationen zu sein, und flip , um eine generische AbstractMatrix Sache zu sein, die nur die Dimensionen einer Matrix umdreht.

Schließlich habe ich nicht viele Beschwerden darüber gesehen, dass transpose(::Matrix{String}) usw. nicht funktioniert. Ist es wirklich ein häufiger Anwendungsfall? In den meisten Fällen sollte es ohnehin besser sein, die gespiegelte Matrix von Anfang an zu erstellen.

Dieses Namensschema (Vorschlag 2) könnte in der Tat besser sein. Adjoint und Transponieren sind in der linearen Algebra ziemlich Standardbegriffe, und es ist weniger brechend ...

Es konnte jedoch nicht festgestellt werden, zu welchem ​​Zeitpunkt entschieden / motiviert wurde, dass eine mathematische Transponierung rekursiv sein sollte

@Jutho Es ist das gleiche Argument wie der Adjunkt (der Adjunkt einer komplexen Zahl ist das Konjugat (da komplexe Zahlen gültige Vektorräume mit Rang eins und lineare Operatoren sind, gelten das innere Produkt und die adjungierten Konzepte) und der Adjunkt einer Blockmatrix ist rekursiv ...). Menschen möchten beispielsweise lineare Algebra mit verschachtelten, realen Matrizen durchführen. Ich sehe immer noch viel Code, der .' für den "Adjunkt einer realen Matrix oder eines realen Vektors" verwendet, und ich habe dies auch getan. Hier wäre ' nicht nur gültig, sondern auch allgemeiner (funktioniert für Arrays, die einen komplexen Wert haben und / oder verschachtelt sind). Eine echtere Verwendung der rekursiven Transponierung auf verschachtelten komplexen Matrizen geschieht, wenn Ihre Gleichungen Ihnen conj(adjoint(a)) , was relativ seltener ist, aber immer noch vorkommt (es ist selten genug, dass ich froh bin, wenn keine Funktion damit verbunden ist - in der Diskussionen über und anderswo haben verschiedene Leute angefangen, es verschiedene Dinge wie "mathematische Transponierung" zu nennen. Ich habe conjadoint , aber nichts davon ist eine großartige Terminologie (IMO, außer vielleicht transpose selbst). In der linearen Algebra erscheint die nicht rekursive Operation, die die Blöcke einer Blockmatrix neu anordnet, aber nichts mit den Blöcken selbst tut, im Allgemeinen nicht.

Schließlich habe ich nicht viele Beschwerden darüber gesehen, dass transpose(::Matrix{String}) usw. nicht funktioniert. Ist es wirklich ein häufiger Anwendungsfall? In den meisten Fällen sollte es ohnehin besser sein, die gespiegelte Matrix von Anfang an zu erstellen.

Ich denke, dass solche Dinge bei Julias Rundfunkbetrieben durchaus wünschenswert sind. Zum Beispiel ist es ziemlich üblich, das äußere (kartesische) Produkt einiger Daten zu nehmen und es möglicherweise über eine Funktion abzubilden. Anstelle von map(f, product(a, b)) wir also broadcast(f, a, transpose(b)) oder einfach f.(a, b.') . Es ist viel Kraft in wenigen Charakteren.

Meine Gedanken: Um zu vermeiden, dass diesem Bereich noch mehr Funktionsnamen hinzugefügt werden (dh flip ), frage ich mich, ob wir einen Standardwert für die Permutation in permutedims(a) = permutedims(a, (2,1)) haben könnten. Leider ist dies nicht so kurz wie flip , aber deutlich weniger unangenehm als das vollständige Formular permutedims(a, (2,1)) (und hat den Nebeneffekt, dass Benutzer über die allgemeinere Funktion unterrichtet werden).

( @ Sacha0 Vielen Dank für Ihre Beschreibung hier. Zu Ihrer Information: Adjoint und Transpose Wrappern oder RowVector + MappedArray + was auch immer für gespiegelt Matrizen wie früher geplant (vielleicht nur PermutedDimsArray ), ich tendiere immer noch dazu, letztere zu bevorzugen ...)

Eine echtere Verwendung der rekursiven Transponierung auf verschachtelten komplexen Matrizen findet statt, wenn Ihre Gleichungen Ihnen conj(adjoint(a))

Wenn Sie conj(adjoint(a)) in Ihren Gleichungen erhalten haben, woher wissen Sie, dass es tatsächlich conj , die Sie auf Ihre Matrixeinträge anwenden wollten, und vielleicht nicht adjoint , dh vielleicht möchten Sie es tatsächlich adjoint.(adjoint(a)) .

Dies wird mich wahrscheinlich verbannen / ausweisen lassen, aber ich bin kein großer Fan der gesamten rekursiven Idee. Ich bin auch nicht unbedingt dagegen, ich glaube nur nicht, dass es einen mathematisch strengen Grund dafür gibt, da Vektoren und Operatoren / Matrizen nicht rekursiv definiert sind, sondern über Skalarfelder (und auf Wunsch durch Erweiterungsringe) definiert werden auch mit Modulen anstelle von Vektorräumen arbeiten). Und so sollte Base.LinAlg nur in diesem Fall funktionieren. Das Hauptargument

Der Adjunkt eines Vektors muss ein linearer Operator sein, der ihn einem Skalar zuordnet. Das heißt, a '* a muss ein Skalar sein, wenn a ein Vektor ist.

ist nicht kompatibel mit der Funktionsweise von Julia Base: Machen Sie a=[rand(2,2),rand(2,2)] und beobachten Sie a'*a . Es ist kein Skalar. Ist a jetzt eine Matrix, weil es sich um einen mit Matrizen gefüllten Vektor handelt? Das Obige ist nur dann ein Skalar, wenn Sie tatsächlich den Ring von 2x2-Matrizen als Skalare verwenden möchten, über die Sie ein Modul definiert haben. Aber in diesen Zusammenhängen ist mir nicht klar, dass das obige Ergebnis das beabsichtigte ist. Das euklidische innere Produkt wird ohnehin selten im Zusammenhang mit Modulen usw. verwendet, daher denke ich nicht, dass Base sich überhaupt die Mühe machen sollte, das richtige Verhalten dafür zu definieren.

Obwohl die obige Aussage ein Versuch sein könnte, die Motivation über die Interpretation von mit Matrizen gefüllten Matrizen als Blockmatrizen hinaus zu erweitern, denke ich letztendlich, dass die Interpretation der Blockmatrix die einzig wahre Motivation war, adjoint und jetzt Sogar transpose (das niemals im mathematischen Sinne verwendet wird, sondern immer im Sinne der Flip-Matrix-Indizes) ist in erster Linie rekursiv. Wenn Sie selbst einen neuen skalaren Feldtyp definieren, ist es nur eine seltsame Belastung, dass Sie zusätzlich zu conj adjoint und transpose dafür definieren müssen, bevor Sie ihn verwenden können in einer Matrix.

Warum also mit einem so leistungsstarken Typsystem wie in Julia nicht einfach einen dedizierten Typ für Blockmatrizen haben? Und so den Anwalt des Teufels spielen:

  • Wie viele Personen verwenden / verlassen sich tatsächlich auf das aktuelle rekursive Verhalten von Adjoint für eine praktische Arbeit?
  • Gibt es andere Sprachen, die einen solchen rekursiven Ansatz verwenden? (Matlab verwendet Matrizenzellen nicht)

Wie gesagt, ich bin nicht unbedingt dagegen, nur die Gültigkeit in Frage zu stellen (auch nachdem ich die gesamte obige Diskussion gelesen habe), insbesondere für den Fall transpose .

Dies wird mich wahrscheinlich verbannen / ausweisen

Mir ist klar, dass dies ein Witz ist, aber eine unpopuläre Meinung zu haben, wird absolut niemanden verbannen. Nur schlechtes Verhalten, das über einen längeren Zeitraum andauert, kann zum Verbot führen. Auch dann ist das Verbot nicht dauerhaft .

Ich glaube einfach nicht, dass es dafür einen mathematisch strengen Grund gibt, da Vektoren und Operatoren / Matrizen nicht rekursiv definiert sind, sondern über Skalarfelder (und durch Erweiterungsringe, wenn Sie auch mit Modulen anstelle von Vektorräumen arbeiten möchten) ).

Diese Aussage macht für mich keinen Sinn. Ein Vektor von Vektoren oder ein Vektor von Funktionen oder ein Vektor von Matrizen bildet immer noch einen Vektorraum (über einem zugrunde liegenden Skalarfeld), wobei + und * scalar rekursiv definiert sind, und in ähnlicher Weise bildet er sich ein innerer Produktraum, in dem das innere Produkt rekursiv definiert ist. Die Idee, dass Vektoren nur "Spalten von Skalaren" sein können, widerspricht sowohl den Definitionen als auch der allgemeinen Verwendung in Mathematik, Physik und Ingenieurwesen.

Machen Sie a=[rand(2,2),rand(2,2)] und beobachten Sie a'*a . Es ist kein Skalar.

In diesem Fall ist der "Skalarring" von a der Ring von 2x2-Matrizen. Dies ist also ein sprachliches Problem, kein Problem mit der Funktionsweise von Adjoint.

Streiten darüber , wie a'*a sollte funktionieren ab , wie es funktioniert zur Zeit (oder nicht funktioniert) in Julia scheint eher kreisförmig.

Wenn Sie selbst einen neuen skalaren Feldtyp definieren, ist es nur eine seltsame Belastung, dass Sie zusätzlich zu conj adjoint und transpose dafür definieren müssen, bevor Sie ihn verwenden können in einer Matrix

Ich denke, diese Belastung ist eher ästhetisch als praktisch. Wenn Sie damit einverstanden sind, einen Subtyp von Number erstellen, müssen Sie nichts definieren, und selbst wenn Sie glauben, dass Number nicht das Richtige für Sie ist, sind die Definitionen so trivial, dass das Hinzufügen ist kaum eine Belastung.

Warum also mit einem so leistungsstarken Typsystem wie in Julia nicht einfach einen dedizierten Typ für Blockmatrizen haben?

https://github.com/KristofferC/BlockArrays.jl/ Mitwirkende willkommen :)

Ich entschuldige mich für den "Ban" -Witz. Dies wird meine letzte Reaktion sein, um diese Diskussion nicht weiter zu entgleisen. Es ist klar, dass rekursiver Adjoint (aber möglicherweise nicht transponiert) festgelegt ist, und ich versuche nicht, diese Entscheidung rückgängig zu machen. Ich versuche nur, auf einige Inkonsistenzen hinzuweisen.

@StefanKarpinski : Das Beispiel des Sind die 2x2-Matrizen selbst die Skalare oder ist die Definition rekursiv und ist der zugrunde liegende Typ Number der Skalar?

Im ersteren Fall haben Sie tatsächlich ein Modul anstelle eines Vektorraums und es hängt wahrscheinlich vom Anwendungsfall ab, ob Sie conj oder adjoint auf diesen 'Skalaren' möchten, wenn Sie es überhaupt wären mit inneren Produkten und Zusätzen überhaupt.

Ich kann die letztere Definition gut akzeptieren. Ich verwende in meinem Job beliebige Elemente in gruppeninvarianten Teilräumen von abgestuften Tensorprodukten von Supervektorräumen, daher bin ich bei meiner Definition von Vektoren nicht wirklich auf Zahlenspalten beschränkt. Aber Vektoren von Matrizen sind nur reine Vektoren oder sollten sie auch ein gewisses Matrixverhalten erben. Im ersteren Fall sollte dot(v,w) einen Skalar erzeugen, wahrscheinlich durch rekursives Aufrufen von vecdot für seine Elemente. Im letzteren Fall sollte mindestens vecdot(v,w) durch rekursives Handeln einen Skalar erzeugen. Das wäre also eine minimale Lösung, um mit dem rekursiven Adjoint übereinzustimmen.

Es scheint jedoch einfacher zu sein, kein rekursives transpose und es nur für nicht-mathematische Anwendungen zu verwenden, da ich denke, dass es selbst in typischen Matrixgleichungen sehr wenig mit der tatsächlichen mathematischen Transponierung zu tun hat einer linearen Karte.

Ich denke, dot sollte dot rekursiv aufrufen, nicht vecdot , daher würde es für einen Vektor von Matrizen einen Fehler geben, da wir kein kanonisches inneres Produkt für Matrizen definieren.

Ich bin immer überrascht, dass die Transponierung rekursiv ist. Und ich kann mir nicht vorstellen, dass dies bei niemandem der Fall ist. (Ich vermute, dass sich die Ansichten zum Wikipedia-Artikel „Transponieren einer linearen Karte“ aufgrund dieser Diskussion mindestens verdreifacht haben.)

Auch ich habe die rekursiven Definitionen oft als beunruhigend empfunden und allgemein die oben von geteilt (aber

Ich denke, ich habe die Inkonsistenz herausgefunden, die mich hier gestört hat - wir verwenden hier weiterhin Ringe und Felder wie die 2x2-Matrix-Einbettung komplexer Zahlen als Beispiel, aber es funktioniert nicht und motiviert keine rekursive Transponierung (und keinen Zusatz) ).

Lassen Sie mich erklären. Die Dinge, denen ich im Großen und Ganzen zustimme

  1. Skalare sind gültige lineare Operatoren vom Rang 1. Angesichts von Julias leistungsfähigem Typsystem gibt es keinen Grund, warum LinAlg Konzepte wie adjoint nicht zutreffen würden, z. B. sollten wir adjoint(z::Complex) = conj(z) . (Abgesehen von Vektoren und Matrizen von Skalaren können LinAlg Konzepte auch ( vermutlich von Benutzern) auf andere Objekte erweitert werden -
  2. Wir sollten in der Lage sein , irgendwie mit Skalaren mit verschiedenen Darstellungen zu beschäftigen. Das prototypische Beispiel hier ist eine komplexe Zahl z = x + y*im kann als Z = x*[1 0; 0 1] + y*[0 1; -1 0] modelliert werden und die Operationen + , - , * , / und \ bleiben in diesem Isomorphismus erhalten. (Beachten Sie jedoch, dass conj(z) an adjoint(Z) / transpose(Z) / flip(Z) - dazu später mehr).
  3. Blockmatrizen sollten irgendwie möglich sein (Der aktuelle Ansatz basiert standardmäßig auf rekursiven adjoint usw.).

Es scheint vernünftig, dass Base.LinAlg mit 1 und 2 kompatibel ist, aber IMO 3 sollte nur in Base wenn es natürlich passt (andernfalls würde ich eher auf externe Pakete wie https: / zurückgreifen /github.com/KristofferC/BlockArrays.jl).

Mir ist jetzt klar, dass wir 2 und 3 zusammengeführt haben und dies zu einigen Inkonsistenzen führt ( @Jutho hat auch darauf hingewiesen). Im Folgenden möchte ich zeigen, dass 3. die Verwendung von rekursiven adjoint und anderen Operationen gemäß Blockmatrizen nicht zu 2. führt. Der einfachste Weg ist ein Beispiel. Definieren wir eine 2x2-Matrix komplexer Zahlen als m = [z1 z2; z3 z4] , die isomorphe Darstellung M = [Z1 Z2; Z3 Z4] und eine Standard-2x2-Blockmatrix b = [m1 m2; m3 m4] wobei m1 usw. gleich große Quadrate haben Matrizen von Number . Die semantisch korrekten Antworten für allgemeine Operationen sind unten aufgeführt:

| Betrieb | z | Z | m | M | b |
| - | - | - | - | - | - |
| + , - , * | rekursiv | rekursiv | rekursiv | rekursiv | rekursiv |
| conj | conj(z) | Z' oder Z.' oder flip(Z) | conj.(m) | adjoint.(M) (oder transpose.(M) ) | conj.(b) |
| adjoint | conj(z) | Z' oder Z.' oder flip(Z) | flip(conj.(m)) oder rekursiv | flip(transpose.(m)) oder rekursiv | rekursiv |
| trace | z | Z | z1 + z4 | Z1 + Z4 | trace(m1) + trace(m4) |
| det | z | Z | z1*z4 - z2*z3 | Z1*Z3 - Z2*Z3 | det(m1) * det(m4 - m2*inv(m1)*m3) (wenn m1 invertierbar ist, siehe zB Wikipedia ) |

In Anbetracht von Operationen wie trace und det die Skalare zurückgeben, ist es meiner Meinung nach ziemlich klar, dass unser Julia-Typ-System für LinAlg unsere 2x2-Matrix-Einbettung von Complex unmöglich verarbeiten konnte trace(Z) wobei Z = [1 0; 0 1] 2 , während dies unsere Darstellung von 1 . Ähnliches gilt für rank(Z) .

Eine Möglichkeit, die Konsistenz wiederherzustellen, besteht darin, unsere 2x2-Darstellung explizit als skalar zu definieren, z. B. durch Subtypisierung von Number wie folgt:

struct CNumber{T <: Real} <: Number
    m::Matrix{T}
end
CNumber(x::Real, y::Real) = CNumber([x y; -y x])

+(c1::CNumber, c2::CNumber) = CNumber(c1.m + c2.m)
-(c1::CNumber, c2::CNumber) = CNumber(c1.m - c2.m)
*(c1::CNumber, c2::CNumber) = CNumber(c1.m * c2.m)
/(c1::CNumber, c2::CNumber) = CNumber(c1.m / c2.m)
\(c1::CNumber, c2::CNumber) = CNumber(c1.m \ c2.m)
conj(c::CNumber) = CNumber(transpose(c.m))
zero(c::CNumber{T}) where {T} = CNumber(zero(T), zero(T))
one(c::CNumber{T}) where {T} = CNumber(one(T), one(T))

Mit dieser Definition funktionieren generische LinAlg -Methoden wahrscheinlich einwandfrei.

Die Schlussfolgerung, die ich daraus ziehe: rekursives adjoint ist eine Annehmlichkeit für Blockmatrizen und Vektoren von Vektoren. Es sollte nicht durch mathematische Korrektheit für die Arten von "skalaren" 2x2-Matrizen motiviert sein, die ich oben mit Z . Ich sehe es als unsere Wahl an, ob wir standardmäßig Blockmatrizen unterstützen oder nicht, mit den folgenden Vor- und Nachteilen:

Vorteile

  • Bequemlichkeit für Benutzer von Blockarrays
  • Vektoren von Vektoren sind gültige Vektorräume, und Blockmatrizen sind gültige lineare Operatoren unter + , * , conj usw. Wenn es möglich ist, dies zu einem natürlichen Isomorphismus zu machen (Im Gegensatz zu dem obigen Beispiel Z , für das CNumber erforderlich ist), warum dann nicht?

Nachteile

  • Erhebliche zusätzliche Komplexität bei der Implementierung von LinAlg (die möglicherweise in einem anderen Paket enthalten sein könnte).
  • Es ist ein bisschen schwierig (aber nicht unmöglich), Dinge wie eig(block_matrix) . Wenn wir sagen, dass LinAlg eig und LinAlg Blockmatrizen unterstützt, dann halte ich dies für einen Fehler, bis er behoben ist. Angesichts der großen Menge an Funktionen, die LinAlg bietet, ist es schwer zu erkennen, dass wir jemals "fertig" sein werden.
  • Unannehmlichkeiten für Datenbenutzer, die Vorgänge wie transpose nicht rekursiv verwenden möchten,

Für mich ist die Frage: Sagen wir, dass LinAlg standardmäßig erwartet, dass die Elemente von AbstractArray s "skalar" sind (Subtypen oder Ententypisierungen von Number )? und die Komplexität von Block-Arrays auf externe Pakete übertragen? Oder nehmen wir die Komplexität in Base und LinAlg ?

Der Plan bis vor einem Tag war der letztere gewesen.

@ Sacha0 Ich habe versucht, die erforderlichen RowVector Arbeiten durchzuführen , um die Änderungen hier zu unterstützen (die früheren: nicht rekursive transpose , RowVector unterstützende Zeichenfolgen und andere Daten) und jetzt frage ich mich, was Sie in Bezug auf Design im Sinn hatten.

Aus den jüngsten Diskussionen sehe ich diese Fälle mit einer Vermutung, was erwünscht sein könnte

| | Vektor | Matrix |
| - | - | - |
| adjoint | RowVector mit rekursiven adjoint | AdjointMatrix oder TransposedMatrix mit rekursivem adjoint |
| transpose | RowVector mit rekursiven transpose | TransposeMatrix mit rekursiven transpose |
| flip | eine Kopie oder PermutedDimsArray ? | eine Kopie oder PermutedDimsArray ? |
| conj von AbstractArray | faul oder eifrig? | faul oder eifrig? |
| conj von RowVector oder TransposedMatrix | faul | faul |

(Die obigen Versuche versuchen, Bedenken hinsichtlich der linearen Algebra von der Permutation von Dimensionen von Datenfeldern zu trennen.)

Also ein paar grundlegende Fragen, um mich loszuwerden:

  • Machen wir rekursive transpose oder nicht? Was ist mit adjoint ?
  • Wenn ja, werden wir weiterhin conj(transpose(array)) == adjoint(array) annehmen?
  • Es scheint, dass zumindest einige komplexe Konjugationen faul sein werden, z. B. alle aktuellen BLAS-Operationen ohne zusätzliche Kopien zu unterstützen. Machen wir conj für alle Arrays faul?
  • Wenn wir flip einführen, ist es faul oder eifrig?

Zu Ihrer Information Ich habe einen reibungsarmen Ansatz versucht, um die Dimensionen einer Matrix bei # 24839 zu "spiegeln", wobei ich eine kürzere Form für permutedims .

Ich bin nachdrücklich für den Vorschlag 2 von @ Sacha0 . Wir haben die theoretischen Grundlagen von rekursivem und nicht rekursivem Adjunkt nicht vollständig conj∘adjoint ) folgen, wenn dies überhaupt erforderlich ist.

FWIW, Mathematica führt weder rekursive Transpose noch ConjugateTranspose :

untitled

Ich habe versucht, die ausführlichen Diskussionen oben zu lesen, konnte aber nicht erkennen, an welchem ​​Punkt entschieden / motiviert wurde, dass eine mathematische Transponierung rekursiv sein sollte. [...] Könnte dieses Konzept aus Gründen der Vollständigkeit / Selbstständigkeit und warum es rekursiv sein sollte, kurz wiederholt / zusammengefasst werden?

Ich verstehe und schätze dieses Gefühl und möchte es ansprechen, soweit es die Zeit erlaubt. Zugänglich und klar zu erklären, warum Adjoint und Transponieren per Definition rekursiv sein sollten, ist leider bestenfalls eine Herausforderung und wahrscheinlich nicht in Kürze möglich. Kohärente, umfassende Beschreibungen wie die oben genannten benötigen enorm viel Zeit für die Erstellung. Da dies kurz ist, werde ich im Laufe des Tages stattdessen versuchen, einige der Verwirrungen in den vorhergehenden Beiträgen zu beheben, indem ich auf bestimmte Punkte / Fragen darin antworte. Bitte ertrage es mit mir, während ich es Stück für Stück mache :). Beste!

Erklären, warum adjoint ... per Definition rekursiv sein sollte ...

Ich bin ziemlich jeder, der zu dieser Diskussion beigetragen hat, stimmt zu, dass adjoint(A) rekursiv sein sollte. Der einzige Streitpunkt ist, ob transpose(A) auch rekursiv sein sollte (und ob eine neue Funktion flip(A) für die nicht wiederkehrende Transponierung eingeführt werden sollte).

Ich bin ziemlich sicher, dass jeder, der zu dieser Diskussion beigetragen hat, der Meinung ist, dass Adjunkt (A) rekursiv sein sollte.

Siehe z. B. https://github.com/JuliaLang/julia/issues/20978#issuecomment-347777577 und frühere Kommentare :). Beste!

Das Argument für rekursiven Adjunkt ist für die Definitionen ziemlich grundlegend. dot(x,y) sollte ein inneres Produkt sein, dh einen Skalar erzeugen, und die Definition des Zusatzes lautet dot(A'*x, y) == dot(x, A*y) . Aus diesen beiden Eigenschaften folgt die Rekursion (sowohl für dot als auch für adjoint ).

(Die Beziehung zu Punktprodukten ist der ganze Grund, warum Adjunkt und Transponieren wichtige Operationen in der linearen Algebra sind. Deshalb haben wir einen Namen für sie und nicht für andere Operationen wie das Drehen von Matrizen um 90 Grad ( rot90 in Matlab) ).)

Beachten Sie, dass ein Problem bei der Verwendung von flip für nicht rekursive Transponierung darin besteht, dass sowohl Matlab als auch Numpy flip für das verwenden, was wir flipdim .

Ich denke, dass niemand die Transponierung im abstrakten mathematischen Sinne wirklich als Operation für lineare Karten verwendet, aus dem einfachen Grund, dass die Transponierung einer Matrix ein Objekt wäre, das nicht auf Vektoren, sondern auf RowVectors wirkt, dh RowVector abbildet zu RowVector.

Es scheint jedoch einfacher zu sein, keine rekursive Transponierung zu haben und sie nur für nicht-mathematische Anwendungen zu verwenden, da ich denke, dass sie selbst in typischen Matrixgleichungen sehr wenig mit der tatsächlichen mathematischen Transponierung einer linearen Karte zu tun hat.

transponieren (wird nie im mathematischen Sinne verwendet, sondern immer im Sinne der Flip-Matrix-Indizes)

Dies wird ein bisschen Kreisverkehr scheinen, also ertrage es mit mir :).

Julias adjoint bezieht sich speziell auf den hermitianischen Adjoint . Im Allgemeinen ist für U und V vollständige normierte Vektorräume (Banach-Räume) mit entsprechenden Doppelräumen U * und V und für die lineare Karte A: U -> V der Adjunkt von A, typischerweise mit A bezeichnet , eine lineare Karte A. *: V * -> U * . Das heißt, im Allgemeinen ist der Adjunkt eine lineare Abbildung zwischen zwei Räumen, ebenso wie im Allgemeinen die Transponierte A ^ t eine lineare Abbildung zwischen zwei Räumen ist, wie oben erwähnt. Wie bringt man diese Definitionen mit dem bekannten Begriff des hermitianischen Adjunkts in Einklang? :) :)

Die Antwort liegt in der zusätzlichen Struktur der Räume, in denen man normalerweise arbeitet, nämlich in vollständigen inneren Produkträumen (Hilbert-Räumen). Ein inneres Produkt induziert eine Norm, so dass ein vollständiger innerer Produktraum (Hilbert) ein vollständiger normierter Raum (Banach) ist und damit das Konzept des (hermitianischen) Adjunkts unterstützt. Hier ist der Schlüssel: Mit einem inneren Produkt und nicht nur einer Norm gilt einer der schönsten Sätze in der linearen Algebra, nämlich der Riesz-Repräsentationssatz. Kurz gesagt, der Riesz-Repräsentationssatz besagt, dass ein Hilbert-Raum von Natur aus isomorph zu seinem dualen Raum ist. Folglich identifizieren wir bei der Arbeit mit Hilbert-Räumen im Allgemeinen die Räume und ihre Dualen und lassen die Unterscheidung fallen. Wenn Sie sich identifizieren, gelangen Sie zu dem bekannten Begriff des hermitischen Adjunkts als A *: V -> U und nicht als A *: V * -> U * .

Und die gleiche Identifikation wird im Allgemeinen für die Transponierung vorgenommen, wenn Hilbert-Räume betrachtet werden, so dass auch A ^ t: V -> U, was den bekannten Begriff der Transponierung ergibt. Um zu verdeutlichen, ja, der gemeinsame Begriff der Transponierung ist der allgemeine Begriff der Transponierung, der auf die bekannteste (Hilbert) Einstellung angewendet wird, genauso wie der gemeinsame Begriff des hermitischen Adjunkts der allgemeine Begriff des Adjunkts ist, der auf diese Einstellung angewendet wird. Beste!

Entschuldigung für das Schlagen eines toten Pferdes, aber ich dachte, ich würde die Probleme der mathematischen Verwirrung auf eine andere Weise kurz zusammenfassen. Der entscheidende Punkt bei Meinungsverschiedenheiten ist die mathematische Interpretation eines Objekts Vector{T} wenn T nicht nur ein Subtyp von Number ohne Array-ähnliche Unterstruktur ist.

Eine Denkschule akzeptiert @stevengjs Behauptung, dass

Ein Vektor von Vektoren über einem Ring R wird am besten als ein direkter Summenraum verstanden, der auch ein Vektorraum über R ist.

Modulo bestimmte Feinheiten in Bezug auf unendlich dimensionale Vektorräume usw. bedeutet dies im Grunde nur, dass wir Vektoren von Vektoren als Blockvektoren betrachten sollten. Ein Vector{Vector{T<:Number}} sollte also mental zu einem einfachen Vector{T} "abgeflacht" werden. Innerhalb dieses Paradigmas sollten lineare Operatoren, die als Matrizen von Matrizen dargestellt werden, ähnlich als Blockmatrizen betrachtet werden, und adjoint sollte sicherlich rekursiv sein, ebenso wie transpose wenn wir das Wort in der verwenden mathematischer Sinn . Bitte korrigieren Sie mich, wenn ich falsch liege, aber ich glaube, dass die Leute, die diesen Standpunkt vertreten, der Meinung sind, dass so etwas wie Vector{Matrix{T}} keine natürliche Interpretation hat, die wir dafür entwerfen sollten. (Insbesondere sollten wir nicht einfach annehmen, dass das innere Matrix eine Matrixdarstellung einer komplexen Zahl ist, denn wie @stevengj sagte,

Wenn Sie komplexe Zahlen durch 2x2-Matrizen darstellen, haben Sie einen anderen komplexierten Vektorraum.

)

Die andere Denkrichtung ist, dass ein Vector{T} immer an eine Darstellung eines Vektors in einem abstrakten Vektorraum über Skalaren (im Sinne der linearen Algebra des Wortes) vom Typ T gedacht werden sollte. unabhängig vom Typ T . In diesem Paradigma sollte ein Vector{Vector{T'}} nicht als Element eines direkten Summenraums betrachtet werden, sondern als Vektor über den Skalaren Vector{T'} . In diesem Fall sollte transpose(Matrix{T}) nicht rekursiv sein, sondern einfach die äußere Matrix umdrehen. Ein Problem bei dieser Interpretation ist, dass, damit die Elemente vom Typ T einen gültigen Ring von Skalaren bilden, ein genau definierter Begriff der (kommutativen) Addition und Multiplikation vorhanden sein muss. Für einen Vektor wie Vector{Vector{T'}} benötigen wir eine Regel zum Multiplizieren von zwei "Skalaren" Vector{T'} mit einem anderen Vector{T'} . Während man sich sicherlich eine solche Regel ausdenken könnte (z. B. elementweise Multiplikation, die das Problem auf das Multiplizieren von T' reduziert), gibt es keinen universellen natürlichen Weg, dies zu tun. Ein weiteres Problem ist, wie adjoint unter dieser Interpretation funktionieren würde. Der hermitische Adjunkt wird nur für lineare Operatoren über einem Hilbert-Raum definiert, dessen Skalarfeld per Definition entweder die reellen oder die komplexen Zahlen sein muss. Wenn wir also adjoint auf eine Matrix Matrix{T} anwenden möchten, müssen wir davon ausgehen, dass T eine Darstellung der komplexen Zahlen (oder reellen Zahlen) ist, aber ich bleibe einfach mit komplex, weil dieser Fall subtiler ist). In dieser Interpretation sollte adjoint nicht rekursiv sein, sondern die äußere Matrix umdrehen und dann conjugate anwenden. Hier gibt es jedoch weitere Probleme, da die korrekte Aktion von conjugate(T) von der Art der Darstellung abhängt. Wenn es sich um die in Wikipedia beschriebene 2x2-Darstellung handelt, sollte conjugate die Matrix umdrehen. Aber aus den oben beschriebenen Gründen sollte conjugate definitiv nicht immer Matrizen umdrehen. Die Implementierung dieses Ansatzes wäre also ein bisschen chaotisch.

Hier sind meine eigenen Gedanken: Es gibt keine "objektiv korrekte" Antwort darauf, ob transpose rekursiv sein sollte, wenn es auf Arrays angewendet wird, deren Elemente eine weiter komplizierte Unterstruktur aufweisen. Dies hängt davon ab, wie genau der Benutzer seine abstrakte algebraische Struktur in Julia darstellt. Die Unterstützung völlig beliebiger Skalarringe scheint jedoch sehr schwierig zu sein. Aus praktischen Gründen sollten wir daher nicht versuchen, so ehrgeizig zu sein, und die esoterische Mathematik von Modulen gegenüber nicht standardmäßigen Ringen vergessen. Wir sollten auf jeden Fall eine Funktion in Base (mit einer einfacheren Syntax als permutedims(A, (2,1)) ), die nichts mit dem Konzept der linearen Algebra der Transposition zu tun hat und einfach Matrizen umdreht und nichts rekursiv macht, unabhängig davon, ob dies der Fall ist genannt transpose oder flip oder was. Es wäre schön, wenn adjoint und eine separate Transponierungsfunktion (möglicherweise mit einem anderen Namen) in LinAlg rekursiv wären, da sie dann Blockvektoren / Matrizen und die einfache Implementierung der direkten Summe handhaben könnten als Vektoren von Vektoren, aber dies ist für die "objektive mathematische Korrektheit" nicht erforderlich, und es wäre in Ordnung, diese Entscheidung nur aus Gründen der einfachen Implementierung zu treffen.

Trotz meines vorherigen Versprechens, keinen Kommentar mehr abzugeben, muss ich leider auf dieses antworten.

Julias Adjunkt bezieht sich speziell auf den hermitianischen Adjunkt. Im Allgemeinen ist für U und V vollständige normierte Vektorräume (Banach-Räume) mit entsprechenden Doppelräumen U * und V und für die lineare Karte A: U -> V der hermitische Adjunkt von A, typischerweise mit A bezeichnet , eine lineare Karte A *: V * -> U *. Das heißt, im Allgemeinen ist der hermitische Adjunkt eine lineare Abbildung zwischen zwei Räumen, ebenso wie im Allgemeinen die Transponierte A ^ t eine lineare Karte zwischen zwei Räumen ist, wie oben erwähnt. Wie bringen Sie diese Definitionen mit dem bekannten Begriff des hermitianischen Adjunkts in Einklang? :) :)

Wirklich, das ist nicht wahr. Was Sie hier beschreiben, ist wirklich die Transponierung, aber (wie ich bereits erwähnt habe) wird dies in einigen Bereichen als Adjunkt (ohne Hermitian) bezeichnet und als A ^ t oder A ^ * (niemals A ^ Dolch) bezeichnet. Tatsächlich geht es weit über Vektorräume hinaus, und in der Kategorietheorie existiert ein solches Konzept in jeder monoidalen Kategorie (z. B. der Kateogorie Cob von n-dimensional orientierten Mannigfaltigkeiten mit Cobordismen als lineare Karten), wo es als adjungierter Partner bezeichnet wird (in Tatsächlich kann es zwei verschiedene A und A geben , da der linke und der rechte Doppelraum nicht unbedingt gleich sind. Beachten Sie jedoch, dass dies niemals eine komplexe Konjugation beinhaltet. Die Elemente von V * sind in der Tat die linearen Karten f: V-> Skalar, und für eine lineare Karte A: U-> V und einen Vektor v von U gilt f (Av) = (A ^ tf) (v) . Da die Wirkung von f keine komplexe Konjugation beinhaltet, beinhaltet auch die Definition von A ^ t keine komplexe Konjugation.

Die Antwort liegt in der zusätzlichen Struktur der Räume, in denen Sie normalerweise arbeiten, nämlich in vollständigen inneren Produkträumen (Hilbert-Räumen). Ein inneres Produkt induziert eine Norm, so dass ein vollständiger innerer Produktraum (Hilbert) ein vollständiger normierter Raum (Banach) ist und damit das Konzept des hermitischen Adjunkts unterstützt. Hier ist der Schlüssel: Mit einem inneren Produkt und nicht nur einer Norm gilt einer der schönsten Sätze in der linearen Algebra, nämlich der Riesz-Repräsentationssatz. Kurz gesagt, der Riesz-Repräsentationssatz besagt, dass ein Hilbert-Raum von Natur aus isomorph zu seinem dualen Raum ist. Folglich identifizieren wir bei der Arbeit mit Hilbert-Räumen im Allgemeinen die Räume und ihre Dualen und lassen die Unterscheidung fallen. Wenn Sie diese Identifikation vornehmen, gelangen Sie zu dem bekannten Begriff des hermitischen Adjunkts als A *: V -> U und nicht als A *: V * -> U *.

Auch hier denke ich nicht, dass das völlig richtig ist. In inneren Produkträumen ist das innere Produkt eine sesquilineare Form dot aus konj (V) x V -> Skalar (mit konj (V) der konjugierte Vektorraum). Dies ermöglicht es tatsächlich, eine Abbildung von V nach V * (oder technisch von konj (V) nach V *) zu erstellen, was in der Tat der Riesz-Repräsentationssatz ist. Wir brauchen das jedoch nicht, um den hermitianischen Adjoint einzuführen. Wirklich, das innere Produkt dot ist selbst ausreichend, und der hermitische Adjunkt einer linearen Karte A ist so, dass
dot(w, Av) = dot(A' w, v) . Dies beinhaltet eine komplexe Konjugation.

Wirklich, das ist nicht wahr. Was Sie hier beschreiben, ist wirklich die Transponierung, aber (wie ich bereits erwähnt habe) wird dies in einigen Bereichen als Adjunkt (ohne Hermitian) bezeichnet und als A ^ t oder A ^ * (niemals A ^ Dolch) bezeichnet. [...]

@Jutho , siehe zum Beispiel die Wikipedia-Seite zum hermitianischen Adjoint .

Vielleicht gibt es Inkonsistenzen zwischen verschiedenen Bereichen der Mathematik, aber:
https://en.wikipedia.org/wiki/Transpose_of_a_linear_map
und besonders
https://en.wikipedia.org/wiki/Transpose_of_a_linear_map#Relation_to_the_Hermitian_adjoint
und unzählige Referenzen in der Kategorietheorie, z
https://arxiv.org/pdf/0908.3347v1.pdf

https://en.wikipedia.org/wiki/Transpose_of_a_linear_map
und besonders
https://en.wikipedia.org/wiki/Transpose_of_a_linear_map#Relation_to_the_Hermitian_adjoint

@Jutho , ich sehe keine Inkonsistenz zwischen diesem Seitenabschnitt und den Definitionen auf der Seite, die ich oben verlinkt habe, und ich sehe auch keine Inkonsistenz mit dem, was ich oben gepostet habe. Beste!

Ich werde mich auch bei @ Sacha0s Vorschlag 2 permutedims Moment einverstanden . Ich denke, das ist besser als flip .

@ Sacha0 , dann haben wir eine andere Art, das zu interpretieren. Ich habe das als gelesen
Für ein gegebenes A: U-> V,
transponieren (A) = dual (A) = (manchmal auch) adjoint (A): V * -> U *
Einsiedler-Adjunkt (A) = Dolch (A) = (typischerweise nur) Adjunkt (A): V-> U.
und die Beziehung zwischen beiden wird genau erhalten, indem die Karte vom Raum zum dualen Raum (dh Riesz ...) verwendet wird, die eine komplexe Konjugation beinhaltet. Daher beinhaltet der hermitische Adjunkt die Konjugation, die Transponierung nicht.

Wenn Sie auch den ersten Einsiedler anrufen möchten, wie nennen Sie dann Transponieren? Sie haben nicht wirklich definiert, was das in Ihrer Beschreibung war, Sie haben es gerade erwähnt

Der hermitische Adjunkt von A, typischerweise mit A bezeichnet , ist eine lineare Abbildung A : V * -> U *. Das heißt, im Allgemeinen ist der hermitische Adjunkt eine lineare Abbildung zwischen zwei Räumen, genauso wie im Allgemeinen die Transponierte A ^ t eine lineare Karte zwischen zwei Räumen ist

Sind Transponieren und Hermitian also zwei verschiedene Arten der Umwandlung von A: U-> V in eine Karte von V -> U ? Ich würde wirklich gerne weiter darüber diskutieren, aber ich denke, wir machen das besser woanders. Aber wirklich, kontaktieren Sie mich, da ich wirklich sehr interessiert bin, mehr darüber zu erfahren.

Siehe auch http://staff.um.edu.mt/jmus1/banach.pdf für eine Referenz, die im Zusammenhang mit Banach-Räumen verwendet wird, ist wirklich transponiert und nicht hermitisch (insbesondere ist es ein linearer und kein antilinearer) Transformation). Wikipedia (und andere Referenzen) verbinden diese beiden Konzepte wirklich miteinander und verwenden den Begriff des hermitischen Adjunkts in Hilbert-Räumen als Motivation für eine verallgemeinerte Definition des Adjunkts in Banach-Räumen. Letzteres ist jedoch wirklich transponiert (und benötigt weder ein inneres Produkt noch eine Norm). Aber das ist die Transponierte, von der ich gesprochen habe, die niemand wirklich im Computercode verwendet.

Für Julia Base: Ich bin nicht gegen eine rekursive hermitische Konjugation; Ich bin damit einverstanden, dass es oft das Richtige ist. Ich bin mir nur nicht sicher, ob Base versuchen sollte, clevere Dinge zu tun, wenn der Elementtyp nicht Number . Selbst wenn T eine Zahl ist, gibt es in Base keine Unterstützung für die weitaus häufigere Verwendung nichteuklidischer innerer Produkte (und der damit verbundenen modifizierten Definitionen von Adjoint), und ich denke auch nicht, dass dies der Fall sein sollte. Ich denke, die Hauptmotivation waren Blockmatrizen, aber dort denke ich, dass ein Spezialtyp (in Base oder in einem Paket) viel julianischer ist, und wie @andyferris auch erwähnte, ist es nicht wie der ganze Rest von LinAlg unterstützt diesen Begriff von Blockmatrizen, sogar einfache Dinge wie inv (geschweige denn Matrixfaktorisierungen usw.).

Aber wenn die rekursive hermitische Konjugation hier bleiben soll (für mich in Ordnung), dann denke ich, dass dot und vecdot Gründen der Konsistenz rekursiv auf die Elemente einwirken sollten. Derzeit ist dies nicht der Fall: dot ruft x'y für die Elemente auf (was nicht dasselbe ist, wenn die Elemente Matrizen sind) und vecdot ruft dot auf die Elemente. Für einen Matrizenvektor gibt es also eigentlich keine Möglichkeit, ein skalares Ergebnis zu erhalten. Ich würde gerne eine PR vorbereiten, wenn die Leute zustimmen, dass die aktuelle Implementierung nicht wirklich mit rekursiven adjoint unvereinbar ist.

Was transpose betrifft, scheint es einfach einfacher zu sein, es nicht rekursiv zu machen und es auch von jenen verwenden zu lassen, die nicht mit numerischen Daten arbeiten. Ich denke, die meisten Leute, die mit Matrizen arbeiten, kennen den Begriff transpose und werden danach suchen. Es gibt immer noch conj(adjoint()) für diejenigen, die ein rekursives transpose benötigen.

Triage meint, @ Sacha0 sollte Vorschlag 2

Ich stimme @ttparker voll und ganz zu, dass rekursiver Adjoint eine Wahl ist und nicht die einzige mathematisch konsistente Option. Zum Beispiel könnten wir einfach sagen:

1 - bis LinAlg ist ein AbstractVector v ein length(v) -dimensionaler Vektor mit (skalaren) Basisgewichten v[1] , v[2] , ..., v[length(v)] .

(und ähnlich für AbstractMatrix ).

Dies wäre wahrscheinlich die Annahme, die viele Leute aus anderen Bibliotheken machen würden, und eine so einfache Definition von Basisvektoren, Rang usw. hilft, die Implementierung einfach zu halten. (Viele würden wahrscheinlich sagen, dass numerische lineare Algebra auf einem Computer implementiert werden kann, gerade weil wir eine gute Basis haben, auf der wir arbeiten können.)

Unser aktueller Ansatz ist eher wie folgt:

2 - bis LinAlg ist ein AbstractVector v eine direkte Summe von length(v) abstrakten Vektoren. Wir fügen auch genügend Definitionen für Skalartypen wie Number so dass sie für LinAlg gültige eindimensionale lineare Operatoren / Vektoren sind.

und ähnlich für (Block-) Matrizen. Dies ist weitaus allgemeiner als die linearen Algebra-Implementierungen in MATLAB, Numpy, Eigen usw., und es spiegelt Julias leistungsfähiges Typ- / Versandsystem wider, dass dies sogar möglich ist.

Der übergeordnete Grund, warum ich Option 2 als wünschenswert betrachte, ist wiederum, dass Julias Typ- / Versandsystem es uns ermöglicht, ein viel breiteres Ziel zu erreichen, das vage lautet:

3 - In LinAlg versuchen wir, eine generische lineare Algebra-Schnittstelle zu erstellen, die für Objekte funktioniert, die die Linearität (usw.) unter + , * , conj erfüllen.

Was ein wirklich cooles Ziel ist (sicherlich weit über jede andere mir bekannte Programmiersprache / Bibliothek hinaus), motiviert rekursive adjoint und 2 (weil + , * und conj sind selbst rekursiv) und deshalb ist @ Sacha0s Vorschlag 2 und die Triage-Entscheidung eine gute Wahl :)

Ich würde wirklich gerne weiter darüber diskutieren, aber ich denke, wir machen das besser woanders. Aber wirklich, kontaktieren Sie mich, da ich wirklich sehr interessiert bin, mehr darüber zu erfahren.

Prost, lass uns machen! Ich freue mich darauf, weiter offline zu chatten :). Beste!

Schöne Zusammenfassung Andy! :) :)

Völlig einverstanden, Andy, zumindest für adjoint (das war das Thema Ihres Kommentars).

Ein letztes Plädoyer für ein nicht rekursives transpose , bevor ich (hoffentlich) für immer meinen Frieden halte.
Ich sehe eine nicht rekursive Transponierte mit folgenden Vorteilen:

  • Es kann von allen Personen verwendet werden, die mit Matrizen arbeiten, auch wenn sie nicht numerische Daten enthalten. Auf diese Weise werden sie diese Operation wahrscheinlich kennen und danach suchen, aus anderen Sprachen und aus der auf ihren nicht numerischen Anwendungsfall extrapolierten Grundmathematik
  • Sie müssen keinen zusätzlichen Code schreiben, damit der faule Typ flip oder PermutedDimsArray mit LinAlg interagiert. Was ist, wenn ich eine numerische Matrix habe, die ich gespiegelt anstatt transponiert habe? Kann ich es trotzdem mit anderen Matrizen multiplizieren (vorzugsweise mit BLAS)?

  • Mit einem nicht rekursiven transpose und einem rekursiven adjoint können wir leicht einen nicht rekursiven Adjoint als conj(transpose(a)) und eine rekursive Transponierte conj(adjoint(a)) . Und trotzdem wird alles gut mit LinAlg interagieren.

Also, was sind die Nachteile. Ich sehe keine. Ich stehe immer noch zu meinem Standpunkt, dass wirklich niemand transpose im mathematischen Sinne verwendet. Aber anstatt weiter zu argumentieren, kann mir jemand ein konkretes Beispiel geben, wo eine rekursive Transponierung erforderlich oder nützlich ist und wo es sich wirklich um eine mathematische Transponierung handelt? Dies schließt jedes Beispiel aus, in dem Sie tatsächlich beabsichtigten, adjoint aber nur reelle Zahlen haben. Eine Anwendung, bei der es einen mathematischen Grund gibt, einen Vektor oder eine Matrix zu transponieren, die mit mehr Matrizen gefüllt sind, die selbst einen komplexen Wert haben.

Ich kann sagen, dass zumindest Mathematica (von dem man erwarten würde, dass es sich erhebliche Gedanken darüber gemacht hat) keine rekursive Transponierung durchführt:

A = Array[1 &, {2, 3, 4, 5}];
Dimensions[A]  # returns {2, 3, 4, 5}
Dimensions[Transpose[A]] # returns {3, 2, 4, 5}

EDIT: Ups, das wurde auch oben kommentiert, sorry

Also bin ich verwirrt. Es schien ein ziemlich solider Konsens darüber zu bestehen, dass transpose nicht rekursiv gemacht werden sollte - z. B. https://github.com/JuliaLang/julia/issues/20978#issuecomment -285865225, https://github.com/ JuliaLang / julia / issue / 20978 # issuecomment -285942526, https://github.com/JuliaLang/julia/issues/20978#issuecomment -285993057, https://github.com/JuliaLang/julia/issues/20978#issuecomment - 348464449 und https://github.com/JuliaLang/julia/pull/23424. Dann gab @ Sacha0 zwei Vorschläge, von denen einer die Transponierung rekursiv flip -Funktion einführen würde, die starke Unterstützung erhielt, obwohl sie (soweit ich das beurteilen kann) zuvor nicht wirklich als Möglichkeit angesprochen worden war . Dann schlug @JeffBezanson vor, dass wir flip doch nicht brauchen, wenn wir permutedims ein zweites Standardargument geben, das ebenfalls starke Unterstützung erhielt.

Nun scheint der Konsens zu sein, dass die einzigen wirklichen Änderungen an transpose "hinter den Kulissen" sein sollten: in Bezug auf spezielle Absenkungen und faule vs. eifrige Bewertungen, die der typische Endbenutzer wahrscheinlich nicht kennt oder sich nicht darum kümmert . Die einzigen wirklich sichtbaren Änderungen sind im Wesentlichen nur Rechtschreibänderungen (Abwertung von .' und Angabe von permutedims als zweites Standardargument).

Der Community-Konsens scheint sich also in sehr kurzer Zeit um fast 180 Grad geändert zu haben (ungefähr zur Zeit von @ Sacha0s Post https://github.com/JuliaLang/julia/issues/20978#issuecomment-347360279). War Sachas Beitrag so beredt, dass er nur die Meinung aller änderte? (Das ist in Ordnung, wenn ja, ich möchte nur verstehen, warum wir uns auf einem Weg vorwärts zu bewegen scheinen, dem wir uns vor ein paar Tagen alle einig waren, dass er der falsche war.)

Ich vergesse, wenn jemand dies vorgeschlagen hat, aber könnten wir transpose(::AbstractMatrix{AbstractMatrix}) (und möglicherweise auch transpose(::AbstractMatrix{AbstractVector}) ) rekursiv und transpose sonst nicht rekursiv machen? Das scheint, als würde es alle Grundlagen abdecken, und ich kann mir keinen anderen Anwendungsfall vorstellen, bei dem tranpose rekursiv sein soll.

Der Konsens in der Community scheint sich also in sehr kurzer Zeit um fast 180 Grad geändert zu haben (ungefähr zur Zeit von @ Sacha0s Post # 20978 (Kommentar)). War Sachas Beitrag so beredt, dass er nur die Meinung aller änderte? (Das ist in Ordnung, wenn ja, ich möchte nur verstehen, warum wir uns auf einem Weg vorwärts zu bewegen scheinen, dem wir uns vor ein paar Tagen alle einig waren, dass er der falsche war.)

Wenn ich nur so beredt wäre 😄. Was Sie sehen, ist, dass sich tatsächlich kein Konsens gebildet hat. Vielmehr kehrten (1) Teilnehmer, die den Status quo befürworteten, sich jedoch aufgrund von Abnutzungserscheinungen aus der Diskussion zurückgezogen hatten, zurück, um eine Stellungnahme abzugeben; und (2) andere Parteien, die nicht darüber nachgedacht hatten, was eine Abkehr vom Status Quo in der Praxis bedeuten würde (und wie dies mit Überlegungen zur Freigabe einhergehen könnte), bildeten eine stärkere Meinung zugunsten des Status Quo und äußerten diese Meinung.

Bitte beachten Sie, dass diese Diskussion in der einen oder anderen Form auf Github seit 2014 und wahrscheinlich früher offline läuft. Für Langzeitteilnehmer werden solche Diskussionen anstrengend und zyklisch. Es gibt sinnvolle Arbeit, die nicht zu erledigen ist, wie sich auf diese Diskussion einzulassen - wie das Schreiben von Code, was mehr Spaß macht -, das Ergebnis ist Abrieb unter diesen Langzeitteilnehmern. Folglich erscheint das Gespräch während der einen oder anderen Periode schief. Persönlich bin ich ungefähr an dieser Abnutzungsschwelle, also werde ich mich jetzt darauf konzentrieren, Code zu schreiben, anstatt mich weiter auf diese Diskussion einzulassen. Vielen Dank und alles Gute! :) :)

Ich werde eine kleine Stimme für nicht rekursive Transponierung und ctransponieren für AbstractArrays abgeben, wobei beide auf AbstractArray {T} rekursiv sind, wobei T <: AbstractArray.

Ich stimme zu, dass rekursives Verhalten in einigen Fällen "korrekt" ist, und ich sehe die Frage darin, wie wir das richtige Verhalten mit der geringsten Überraschung für diejenigen erreichen, die Pakete verwenden und entwickeln.
In diesem Proprosal ist das rekursive Transponierungsverhalten für benutzerdefinierte Typen aktiviert: Sie aktivieren sich, indem Sie Ihren Typ zu einem AbstractArray machen oder die entsprechende Methode definieren
Base.transpose(AbstractArray{MyType}) oder Base.transpose(AbstractArray{T}) where T<: MyAbstractType .
Ich denke, die Ententypisierungsstrategie für rekursive Transponierungen (nur rekursiv ohne zu fragen) bringt einige Überraschungen hervor, wie oben dokumentiert. Wenn Sie unterschiedliche ctranspose und adjoint oder kompliziertere Vorschläge wie conjadjoint und flip einführen, werden Benutzer auf diese stoßen und versuchen, sie zu verwenden, und Paketbetreuer werden versuchen, sie alle zu unterstützen.

Als Beispiel für etwas, das unter den neuen Vorschlägen schwer zu unterstützen wäre: Normal-, Transponierungs-, Ctransponierungs- und Conj-Arrays sollten alle Ansichten (oder verzögerte Auswertungen) haben können, die mit ReshapedArray- und SubArray-Ansichten zusammenarbeiten. (Ich bin mir nicht sicher, ob diese standardmäßig oder nur bei Verwendung von @view Ansichten erzeugen.) Dies verbindet sich mit der Arbeit an der A*_mul_B* -Einsenkung und an BLAS-Aufrufen auf niedrigerer Ebene mit den Flags 'N'. 'T' und 'C' für dichte Arrays, wie an anderer Stelle erwähnt wurde. Diese wären leichter zu überlegen, wenn sie normal , transpose , ctranspose und conj
auf gleicher Augenhöhe. Beachten Sie, dass BLAS selbst nur 'N' für Normal, 'T' für Transponieren und 'C' für Transponieren unterstützt und kein Flag für Konj. Hat, was ich für einen Fehler halte.

Aus Gründen der Konsistenz mit höherdimensionalen Arrays und Umformen glaube ich, dass die angemessene Verallgemeinerung von Transponierung und Transponierung darin besteht, alle Dimensionen umzukehren, d. H.
transponieren (A :: Array {T, 3}) = permutierte (A, (3, 2, 1)).

Prost!

Ich schätze die Leute, die die Arbeit tatsächlich machen, sehr. Was viel zu lange diskutiert wurde, sind Vektoradjunkte / Transponierte (aber niemals der rekursive Aspekt davon), bis @andyferris dies verstärkte und implementierte, und es funktioniert wunderbar gut. In ähnlicher Weise schätze ich auch die fortlaufende Neugestaltung von Array-Konstruktoren sehr. Daumen hoch für all das.

Abgesehen davon wurden Matrixtransponierte und adjungierte / ctransponierte nie viel diskutiert, insbesondere nicht der rekursive Aspekt, der in https://github.com/JuliaLang/julia/pull/7244 fast stillschweigend als einzelne Motivationsblockmatrizen eingeführt wurde . Verschiedene Gründe und Motivationen für einen rekursiven Adjunkt wurden angegeben (nach den Fakten), und die meisten Menschen können sich darauf einigen, dass dies eine gute (aber nicht die einzige) Wahl ist. Transponieren fehlt jedoch auch nur eine einzige Motivation oder ein tatsächlicher Anwendungsfall.

In diesen Diskussionen gibt es einige getrennte Dinge, und es kommt gerade vor, dass wir einen Plan brauchen, der schnell umgesetzt werden kann.

  • Wir haben diskutiert, ob sich die Unterstützung von Blockmatrizen (und exotischeren Strukturen) in LinAlg lohnt. Die Implementierungsoptionen sind: überhaupt keine rekursiven Inhalte (außer + , * und conj da dies die Natur der generischen Funktionen in Julia ist), alles rekursiv (der Status Quo), oder versuchen Sie eine Art Typprüfung oder ein Merkmal dafür, ob ein Element eine rekursive lineare Algebra ausführen oder als skalar behandelt werden soll.
  • Wir möchten eine gute Möglichkeit für Benutzer, die Dimensionen eines 2D-Datenarrays zu permutieren. Wir haben nicht rekursive transpose , flip , verkürzte permutedims -Syntax (diese PR wurde zuerst eingereicht, nur weil es die wenigsten zu implementierenden Zeichen sind und wahrscheinlich sogar Sinn machen Wenn wir noch etwas anderes tun, eine Art Typprüfung oder eine Eigenschaft, ob ein Element eine rekursive Transponierung durchführen soll (vielleicht sogar die Wiedereinführung von transpose(x::Any) = x ...).
  • Der Julia-Parser hat ein seltsames Verhalten wie x' * y -> Ac_mul_B(x, y) was eine Art Warze ist, die es in Version 1.0 im Idealfall nicht gibt. Dies wurde nicht als machbar angesehen, bis wir schnelles BLAS (keine zusätzlichen Kopien) ohne es unterstützen können, also faul Matrix transponieren & adjungieren.
  • Der Code in LinAlg ist ziemlich groß und wurde über mehrere Jahre aufgebaut. Viele Dinge wie die Matrixmultiplikation könnten so umgestaltet werden, dass sie charakteristischer sind, vielleicht mit einem Versandsystem, das eher dem neuen broadcast . Ich denke, hier können wir es einfacher machen, die richtigen Arrays (ich denke PermuteDimsArray von konjugierten umgeformten ajdointed Ansichten von Schrittmatrizen) an BLAS zu senden. Dies wird jedoch nicht zu Version 1.0 führen, und wir versuchen auch, Leistungsregressionen zu vermeiden, ohne den Code wesentlich zu verschlechtern. Wie Sacha betonte (und ich entdecke), sorgt die Transponierung von Ansichten mit einer Vielzahl von Verhaltensweisen für die Elemente (rekursiver Adjunkt, rekursive Transponierung, Konjugation, nichts) für zusätzliche Komplexität und eine Reihe neuer Methoden, damit die Dinge so funktionieren, wie sie sind sind.

Wenn wir v1.0 als etwas stabilisierend für die Sprache betrachten, dann ist in gewisser Hinsicht die dritte die größte Priorität, um eine Verhaltensänderung vorzunehmen. Ich würde sagen: Die Sprache (einschließlich Parser) sollte am stabilsten sein, gefolgt von Base , gefolgt von der stdlib (die LinAlg kann oder nicht, aber ich denke, sie wird fast definitiv enthalten BLAS , Sparse usw. eines Tages). Es ist eine Änderung, die Benutzer (hauptsächlich Bibliotheksentwickler) nicht wirklich betrifft, daher wäre ich nicht überrascht, wenn die Meinungen der Leute hier unterschiedlich wären.

Genau richtig für Andy! :) :)

Ich denke, das einzige, was hier noch zu tun ist, ist, adjoint und transpose standardmäßig faul zu machen?

Kann das jetzt geschlossen werden?

Als nächstes: "Skalartransponierungen ernst nehmen"

Aber im Ernst, können wir eine gute Schnittstelle haben, um die verschiedenen 3D-Transponierungen und Tensormultiplikationen zu spezifizieren, die in PDE-Lösern verwendet werden? Ein bisschen ernst, aber ich bin mir nicht sicher, ob ich es schaffen könnte, das OP für die nächste Iteration dieses Wahnsinns zu sein.

Nein

:) :)

Können wir eine gute Schnittstelle haben, um die verschiedenen 3D-Transponierungen und Tensormultiplikationen zu spezifizieren, die in PDE-Lösern verwendet werden?

Scheint definitiv ein gutes Thema für ein Paket zu sein.

Eine gute Schnittstelle zur Angabe der verschiedenen 3D-Transponierungen und Tensormultiplikationen

Tut TensorOperations.jl hier nicht das, was Sie brauchen? (Beachten Sie, dass auf dieser Ebene "eine gute Schnittstelle" so etwas wie ein Tensornetzwerkdiagramm bedeutet, das etwas schwieriger zu schreiben ist als die Syntax von

Ja, TensorOperations.jl sieht gut aus. Ich habe ein bisschen Spaß gemacht, aber ich habe das bekommen, was ich brauchte 👍.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

i-apellaniz picture i-apellaniz  ·  3Kommentare

StefanKarpinski picture StefanKarpinski  ·  3Kommentare

wilburtownsend picture wilburtownsend  ·  3Kommentare

felixrehren picture felixrehren  ·  3Kommentare

arshpreetsingh picture arshpreetsingh  ·  3Kommentare