1.5 Lineare Codes

Nachdem wir in den letzten drei Kapiteln einen Code rein als Untermenge C{0,1}n betrachtet haben und die Fragen der Codierung und Fehlererkennung hinangestellt haben, kehren wir nun zu der ursprünglichen Sichtweise zurück, dass nämlich der Code durch die Codierungsfunktion

E:{0,1}k{0,1}n

gegeben ist. Wir werden nun die Menge {0,1} als Köper mit zwei Elementen F2 auffassen, mit Multiplikation und Addition modulo 2. Dann sind Fk und Fn Vektorräume über F, d.h. wir können Vektoren addieren, z.B.

[0111]+[1010]=[1101]

Wir können nun den Fall untersuchen, dass die Codierungsfunktion E linear ist.

Definition 1.5.1 (Linearer Code). Ein linearer Code ist gegeben durch eine lineare Abbildung E:F2kF2n. Dies ist ein Code der Blocklänge n. Die Dimension des Codes ist k. Die Rate des Codes ist k/n. Wir können die Abbildung E durch eine Matrix GF2n×k mit n Zeilen und k Spalten darstellen, die Generatormatrix des Codes. Wir nennen E einen [n,k]-Code.

Erinnern wir uns an einen ganz einfachen Parity-Check-Code E:{0,1}k{0,1}n mit k=n1: wir hängen dem Urwort x die seine Parität an, also (x1xk)(x1,,xk,x1xk). Dies ist ein linearer Code mit der Generatormatrix

[111111]

Dieser Code ist ein [n,n1]-Code mit Abstand 2.

Übungsaufgabe 1.5.1 Zeigen Sie: wenn E:F2kF2n ein linearer Code ist, dann ist der Abstand Δ(E) gleich

minxF2k{0}|E(x)| ,

wobei |y| die Anzahl der Stellen in y ist, die nicht 0 sind.

Generatormatrix in systematischer Form

Der obige Parity-Check-Code, also der [n,n1]-Code mit Abstand 2, hatte die schöne Eigenschaft, dass jedes Codeword E(x) aus dem Urwort x besteht, gefolgt von Parity-Check-Bits. Das ist nicht immer so. Betrachten wir beispielsweise den [7,4]-Code mit der Generatormatrix

G:=[1110110001100001001101001011]

Sie werden das Urwort xF24 in seinem Bild Gx nicht unmittelbar wiedererkennen:

[1110110001100001001101001011][x1x2x3x4]=[x1+x2+x3x1+x2x2+x3x4x3+x4x2x1+x3+x4]

Wenn Sie allerdings mit dem Gaussschen Eliminierungsverfahren vertraut sind, wird es Sie nicht wundern, dass man die Generatormatrix immer so umformen kann, dass das Urwort ein Präfix des Codewortes ist. Wir demonstrieren dies am Beispiel der obigen Matrix. Konkret wollen wir erreichen, dass die obersten vier Zeilen die Identitätsmatrix bilden. Die Gausssche Eliminierung lässt sich über dem Körper F2 viel besser demonstrieren als über Q, da man nicht mit unhandlichen Brüchen hantieren muss.

Bezeichnen wir die vier Spalten der obigen Matrix mit c1, c2, c3 und c4. Die Matrix G können wir also als [c1|c2|c3|c4] schreiben. In dieser Notation wird aus dem Urwort x=[x1,x2,x3,x4]T das Codewort x1c1+x2c2+x3c3+x4c4, also

E:[x1x2x3x4]x1c1+x2c2+x3c3+x4c4

Ich möchte nun G so umformen, dass die obersten vier Zeilen der Matrix die Identitätsmatrix I4 bilden. Als erstes arbeite ich an der ersten Zeile und möchte sie von derzeit [1,1,1,0] nach [1,0,0,0] umbauen. Dafür addieren wir die erste Spalte zur zweiten, ersetzen also G=[c1|c2|c3|c4] durch G1:=[c1|c1+c2|c3|c4]. Aber Moment: jetzt haben wir eine neue Generatormatrix. Haben wir auch einen neuen Code? Nun ja, wenn wir die Codierungsfunktion betrachten, dann ist xG1x natürlich eine andere Funktion als xGx. Wenn wir allerdings den Code als Untermenge C=img(G)F27 betrachten, dann ändert dieser sich nicht:

Behauptung. 1.5.2 img(G)=img(G1).

Ich könnte natürlich sagen "das ist grundlegende lineare Algebra", aber vielleicht ist es nicht schlecht, es sich noch einmal ins Gedächtnis zu rufen, warum das so ist.

Beweis. Als erstes zeigen wir img(G)img(G1). Sei yimg(G), also y=Gx für ein xF24. Setzen wir u:=[x1x2,x2,x3,x4]T. Dann gilt

G1u=G1[x1x2x2x3x4]=(x1x2)c1+x2(c1+c2)+x3c3+x4c4=x1c1+x2c2+x3c3+x4c4=Gx ,

und somit ist y=Gx=G1u auch in img(G1). Für die Inklusion img(G1)img(G) nehmen wir y=G1x und rechnen das einfach aus:

y=G1x=[c1|c1+c2|c3|c4][x1x2x3x4]=x1c1+x2c1+x2c2+x3c3+x4c4=[c1|c2|c3|c4][x1+x2x2x3x4] und somit gilt yimg(G). Dies zeigt also, dass img(G)=img(G1) ist.

Obwohl wir so getan haben, am konkreten Beispiel von GF27×4 zu arbeiten, wird der Leser bestimmt bestätigen können, dass diese Methode ganz allgemein funktioniert: wenn wir G1 aus G bilden, indem wir eine Spalte zu einer anderen addieren, dann gilt img(G)=img(G1).

G=[1110110001100001001101001011][11+11011+10000+11000+00100+01100+10011+011]=[1010100001100001001101001111]=:G1

Wir entfernen nun die 1 an der dritten Stelle der ersten Zeile, indem wir die erste Spalte auf zur dritten addieren.

G1=[1010100001100001001101001111][101+10101+00010+10000+01000+11010+00111+11]=[1000101001100001001101001101]=:G2

Wir haben also die erste Zeile erfolgreich in [1,0,0,0] umgeformt. Als nächstes wollen wir die zweite Zeile in [0,1,0,0] umformen. Hier haben wir allerdings ein Problem: die ersten beiden Zeilen haben an der zweiten Stelle eine 0. Wir kriegen da also keine 1 hin. Wir könnten nun unser Ziel ändern und die dritte Zeile zu [0,1,0,0] machen. Dann würden wir oben zwar nicht die Identitätsmatrix hinkriegen, aber so etwas ähnliches (eine Permutationsmatrix). Übersichtlicher wird es allerdings, wenn wir für Position (2,2) eine 1 "beschaffen", in dem wir zum Beispiel Zeile 2 und 3 vertauschen.

G2=[1000101001100001001101001101][1000011010100001001101001101]=:G3

Jetzt haben wir an Position (2,2) eine 1 und können fortfahren. Aber halt: ändert dieser Schritt, also das Vertauschen zweier Zeilen, unseren Code? In der Tat ja, allerdings nicht wesentlich. Definieren wir π(23):F27F27 als

π(23):[y1y2y3y4y5y6y7][y1y3y2y4y5y6y7] ,

also die Abbildung, die die zweite und die dritte Koordinate vertauscht, und definieren wir für eine Menge UF27 die Menge π(23)(U):={π(23)(u) | uU}, dann gilt

img(G2)=π(23)(img(G3)) .

Wir erhalten als Bild img(G3) also einen neuen Code C, der im allgemeinen nicht identisch zu C=img(G)=img(G1)=img(G2) ist, sondern aus diesem durch Vertauschen zweier Koordinaten hervorgeht. Es sollte offensichtlich sein, dass |C|=|C| und Δ(C)=Δ(C) gilt und ganz allgemein alle interessanten kombinatorischen Eigenschaften erhalten bleiben. Machen wir weiter:

G3=[1000011010100001001101001101]zweite auf dritte addieren[100+00011+10100+10000+01000+11011+00111+01]=[1000010010100001001101101111]=:G4

Indem wir die zweite Spalte auf die dritte addieren, haben wir die 1 un der zweiten Zeile an dritter Stelle eliminiert. Oberhalb davon, also in der ersten Zeile, geschieht nichts, weil diese Einträge ja bereits 0 geworden sind. Wir haben also die Sicherheit, dass getane Arbeit in den oberen Zeilen nicht wieder zerstört wird. Mit der dritten Zeile nun haben wir Glück: sie hat eine 1 an der dritten Stelle. Wir müssen also keine Koordinaten vertauschen. Wir eliminieren die 1 an Stelle 1 der dritten Zeile, indem wir die dritte Spalte auf die erste addieren:

G4=[1000010010100001001101101111][1+00000+01001+10100+00010+10110+11101+1111]=[1000010000100001101111100111]=:G5

Durch reines Glück ist die vierte Zeile bereits [0,0,0,1] und wir sind nun fertig. Unsere Matrix hat die Form [I4P] wie gewünscht.

Gausssche Umformungsschritte. Rekapitulieren wir kurz, welche Umformungsschritte wir angewandt haben:

  1. Spaltenaddition. Wir addieren eine Spalte zu einer anderen. Dadurch ändert sich img(G) nicht.
  2. Zeilen vertauschen. Wir vertauschen Zeile i mit Zeile j. Dadurch ändert sich img(G), es wird nämlich zu π(ij)(img(G)), aber alle interessanten kombinatorischen Eigenschaften (insbesondere Mindestabstand Δ) bleiben erhalten.

Im allgemeinen Fall werden wir nicht unbedingt über F2 arbeiten, sondern über einem allgemeinen Körper K, meist einem endlichen. Für diesen Fall brauchen wir noch eine dritte Umformungsoperation:

  1. Skalarmultiplikation. Wir ersetzen eine Spalte c durch λc für ein λK{0}.

Für F2 ist der dritte Schritt trivial, weil es neben 0 nur ein Körperelement gibt, die 1.

Können wir steckenbleiben? Wie allgemein ist obiges Verfahren? Kann man steckenbleiben? Wir beschränken uns hier wieder auf den Fall F2, da der von der Notation her einfacher ist aber alle essentiellen Argumente enthält.

Im allgemeinen haben wir bereits durch die Umformungsschritte erreicht, dass Zeilen 1 bis i1 die Einheitszeilenvektoren e1,,ei1 sind. Wenn es nun eine weitere Zeile u gibt, die an der i-ten Stelle eine 1 hat, also ui=1, dann können wir in einem ersten Schritt per Vertauschung sicherstellen, dass dies Zeile i ist. In einem zweiten Schritt entfernen wir alle anderen Einser von Zeile i: für jedes ji mit uj=1 ersetzen wir Spalte cj durch ci+cj. Dadurch wird Stelle j von u zu 0.

Nun ist also die i-te Zeile von u zu ei, dem Zeileneinheitsvektor, geworden. Was ist oberhalb von Zeile i geschehen? Wir haben Spalte ci auf viele andere Spalten addiert. Da aber Zeilen 1 bis i1 die Einheitszeilenvektoren e1,,ei1 sind, sind die oberen i1 Einträge von ci alle 0. Die Additionsschritte haben also in den ersten i1 Zeilen keinen Effekt.

Wir sehen: wir können nur steckenbleiben, wenn es keine Zeile u gibt mit ui=1. Das heißt aber, dass ci=0. In Fachsprache: die Spalten von G sind nicht linear unabhängig; G hat nicht vollen Spaltenrang. Konkret heißt das, dass

Gx=G[x1xixn]=x1c1++xici++xncn(weil ci=0)=x1c1++(xi+1)ci++xncn=Gz ,

wobei sich x und z nur an Stelle i unterscheiden. Die Abbildung xGx ist also nicht einmal injektiv, insbesondere ist der Minimalabstand des Codes 0! Wir sehen: solange GF2n×k vollen Spaltenrang k hat, kommt das Umformungsverfahren zu einem Ende und liefert uns eine neue Generatormatrix in der Form [IkM].

Wir können also jede Generatormatrix GF2n×k umformen in eine Matrix G=[IkM], so dass sich img(G) und img(G) nur in der Anordnung ihrer Koordinaten unterscheiden.

Theorem 1.5.3 (Generatormatrix in systematischer Form). Sei GF2n×k einer Generatormatrix mit Spaltenrang k. Dann gibt es G=[IkM] und eine Permutation π:[n][n] mit

img(G)=π(img(G)) ,

wobei AF2n die Notation π(A):={(aπ(1)aπ(2)aπ(n)) | aA} verwenden.

Insbesondere gilt Δ(G)=Δ(G) und R(G)=R(G), das heißt, in allen Parametern, die uns im Moment interessieren, sind G und G gleichwertig.

Fehlererkennung: die Parity-Check-Matrix

Wenn nun G in systematischer Form vorliegt, können wir für ein gegebenes xF2 auch direkt überprüfen, ob ximg(G) liegt, ob also x ein Codewort ist:

def checkLinearCodeWord(x=[x1xn]T,G=[IkM]): // prüft, ob x ein Codewort ist
  1. u:=[x1,,xk], v=[xk+1,,xn]
  2. if Mu=v return true else return false

Wenn x ein gültiges Codewort ist, dann sind die ersten k Koordinaten das Urwort u (da G in systematischer Form vorliegt). Wir erhalten die "richtigen" weiteren nk Koordinaten durch Mu. Der Algorithmus muss also nur prüfen, ob diese identisch mit den tatsächlich vorliegenden Koordinaten xk+1xn sind. Der Algorithmus prüft also, ob Muv=0 ist. Da x=[uv] gilt, können wir das kompakt wie folgt schreiben:

[M|I(nk)×(nk)]x=0

Die Matrix P:=[M|I(nk)×(nk)]F2(nk)×n ist die Parity-Check-Matrix des Codes.

Theorem 1.5.4 Sei GF2n×k die Generatormatrix eines Codes und habe G vollen Rang k. Dann gibt es eine Matrix PF2(nk)×n, die Parity-Check-Matrix mit der Eigenschaft

ximg(G)Px=0F2nk

für alle xF2n.

Wir erhalten die Parity-Check-Matrix, indem wir G in systematische Form G=[IkM] bringen. Die Parity-Check-Matrix ist dann P=[M|I(nk)×(nk)]. Eine Parity-Check-Matrix P für den Code img(G) erhalten wir dann, indem wir Spalten von P entsprechend permutieren, die Vertauschungen also rückgängig machen.