#import "@preview/codly:1.3.0": * #import "@preview/codly-languages:0.1.1": * #show: codly-init.with() #codly(languages: codly-languages) = Konzeption und Durchführung == Literaturrecherche und Konzeption Zunächst fand eine tiefere Einarbeitung in die existierende Literatur zu alternierenden Optimierungsverfahren statt. Aufgrund der Nähe der Themen hat sich eine Recherche zu den "k-Means Clustering" und "Expectation-maximization" Algorithmen angeboten. Spannende Literatur zu diesen Themen wurde von Bezdek @bezdek_notes_2002 -- allgemein zu alternierenden Optimierungsverfahren und spezifischer von Do @do_what_2008 publiziert. Wenngleich diese Publikationen keinen direkten Weg zur Lösung der Problemstellung der Praxis bieten konnten, stellten sie ein gutes Grundverständnis für diese Art von Problem dar. // Hier noch Unterschiede und Gemeinsamkeiten der Literatur vs. Problem aufstellen in Stichpunkten === Festlegung der verwendeten Toolings Aufgrund der großen Effizienz und der gut ausgebauten Möglichkeit zur funktionalen Programmierung, wurde als Programmiersprache für das Projekt -- zur Implementierung der Algorithmen und Simulation der Bitfehlerrate -- Julia gewählt. Im weiteren Verlauf der Praxis wurde zusätzlich für die verbesserte Möglichkeit der Visualisierung der Ergebnisse das Pluto Framework -- ein Julia-Pendant zu Jupyter Notebooks -- mit einbezogen. == Durchführung und Projektdokumentation Für eine effiziente und übersichtliche Implementierung der Algorithmen und Simulationen wurde zunächst diverse Hilfsfunktionen in Julia implementiert. #figure( caption: "Generierung von allen möglichen Linearkombinationen" )[ ```julia function create_linearcombinations(inputs, weights, n) collect(map( set -> begin LinearCombinationSet( collect(map( weights -> begin LinearCombination(weights, map(v -> [signbit(v)], weights), set, sum(weights .* set)) end, weights ))) end, Iterators.partition(inputs, n))) end ``` ] @code:generate_lincombs zeigt exemplarisch die Implementierung einer Funktion zur Generierung aller möglichen Linearkombinationen für eine Menge an Eingangswerten. Anschließend wurde die betragsmäßige Optimierung, welche in @bach_original_1 und @fig:bach_1_optimal dargestellt wird, implementiert und getestet. === Rekursiver Ansatz Als nächste Möglichkeit für den Multi-Bit Fall ist ein rekursiver Ansatz des Problems implementiert worden. Hierfür werden die Eingangswerte zunächst mit der betragsmäßigen Optimierung verarbeitet um so eine "optimale" Verteilung für den 1-bit Fall zu konstruieren. Anschließend wird die Verteilung in zwei symmetrische Unterverteilungen aufgeteilt, und jeweils deren Mittelwert bestimmt. Anschließend werden für jede Summe der jeweiligen Unterverteilungen zusätzliche fraktionierte Gewichtungen auf die bereits bestehenden Gewichtungen aufaddiert. Basierend auf der Mittelwertbestimmung werden zusätzliche Grenzen definiert, anhand deren die aufkommenden neuen Summen mit fraktionierte Gewichtungen optimal gewählt werden. Basierend auf der Anzahl $m$ an Bits die aus einer Summe extrahiert werden sollen wird dieses Verfahren $m$-Mal mit allen entstehenden Unterverteilungen durchgeführt. Ein erstes positives Ergebnis hier war die schnelle Konvergenz der Verteilung und die Gleichverteilung der quantisierten Symbole, da in jeden Grenzbereich möglichst gleich viele Summen gelegt worden sind. @fig:bach_recursive_dist zeigt das Ergebnis des rekursiven Ansatzes für die Quantisierung von 2 Bit. // Notiz: Grafiken vielleicht nochmal separat exportieren wenn Pluto das irgendwie zulässt. #figure( image("../graphics/execution/recursive_distribution .png", width: 70%), caption: "Verteilung der Eingangswerte nach dem rekursiven Ansatz" ) Jedoch stellte sich nach der Analyse der verwendeten Hilfsdatenvektoren heraus, dass durch die Zuweisung der Hilfsdaten Informationen über den Schlüssel ableitbar sind. #figure( image("../graphics/execution/helperdata_occurs.png", width: 70%), caption: "Verteilung der Hilfsdatenvektoren für jedes Bitsymbol" ) Das Histogramm in @fig:bach_recursive_hd_occurs zeigt dieses Problem auf. Damit über die Hilfsdaten keine Informationen über den Schlüssel bekannt werden, muss jeder verwendete Hilfsdatenvektor von jedem Symbol gleich häufig verwendet werden. Mit diesem Ansatz werden von je zwei Symbolen jedoch nur vier von acht möglichen Hilfsdatenvektoren verwendet. === Vorgabe des Codeworts Eine weitere getestete Methode bestand aus dem vorgeben des zu verwendeten Codewords bzw. Schlüssels. Hierfür werden die Grenzen der Quantisiererfunktion über die kumulative Verteilungsfunktion der Eingangswerte bestimmt. Anschließend wird jene Summe mit Hilfsdaten gewählt, welche die Summe zu ihrem vorgegebenen Codewort quantisieren. #figure( image("../graphics/execution/given_codeword.png", width: 65%), caption: "Verteilung nach der Verarbeitung mit vorgegebenem Codewort" ) Leider stellte sich die Vorgabe, jene Linearkombination zu wählen welche am besten ein vorgegebenes Codewort approximiert nicht als praktikabel heraus, da -- wie in @fig:bach_given_codeword zu sehen ist -- bei einer Verarbeitung für 2 Bit keine vier voneinander unterscheidbaren Unterverteilungen ergeben. Nahe der 0 lässt sich lediglich eine kleine Abweichung vom 1-bit Fall feststellen, welche aber nicht signifikant genug ist, um die Bitfehlerrate zu minimieren. === Brute-Force-Ansatz Als letzten möglichen Lösungsansatz wurde ein Brute-Force-Ansatz untersucht. Um die optimalen Grenzen für eine Quantisierung höherer Ordnung zu erhalten, wurden für verschiedene Quantisierergrenzen die Verteilungen der Quantisierten Codewörter analysiert. Im Detail wurde für eine große Menge an möglichen Grenzen die Distanzmaximierung der Linearkombinationen durchgeführt. // needs citation Direkt im Anschluss wurde über Pearson's Chi-square Test die Gleichverteilung der Quantisierten Symbole überprüft und nach einem Maximum des Ergebnisses des Tests gesucht. @fig:bach_brute_force zeigt das Ergebnis der Verarbeitung dieser Grenzen für einen 3-bit Fall. Da diese Brute-Force Operation sehr rechenaufwändig ist, wurden die bereits in Julia implementierten Lösungen für parallel Computing eingesetzt und die Berechnung der idealen Grenzen auf einem Computer mit hoher Rechenkapazität ausgelagert. #figure( image("../graphics/execution/brute-force.png", width: 80%), caption: "Resultat nach Verwendung der durch den Brute-Force Ansatz gefundenen Grenzen" ) Auch die Betrachtung des Histogramms der Hilfsdatenverteilung zeigt befriedigende Ergebnisse auf. Wie in @fig:bach_brute_force_occurs zu sehen ist, wird jeder Hilfsdatenvektor von jedem Symbol gleich häufig verwendet. #figure( image("../graphics/execution/brute-force-occurs.png", width: 80%), caption: "Resultat nach Verwendung der durch den Brute-Force Ansatz gefundenen Grenzen" ) Außerdem ist über diese Methode eine signifikante Verbesserung de Bitfehlerrate im Bezug auf eine Quantisierung ohne Vorverarbeitung und Fehlerkorrekturcode vorzuweisen, weshalb sich hier gute Chancen auf ein zukünftiges Nutzbares Verfahren abbilden. === Portieren der BCH-Code Python Bibliothek Für die weitere Integration und vollständige Implementierung zu einem allgemeinen Simulationsprogramm, das die gesamte Fehlerkorrektur bis zum Schlüssel abdeckt, ist ein BCH-Fehlerkorrektur-Code als Python-Codebasis vorgegeben worden. Damit dieser auf die bereits in Julia programmierten Implementierungen angewendet werden kann, wurde diese Codebasis vollständig von Python nach Julia portiert. Hierzu waren kleinere Zwischenrecherchen im Bezug auf die Differenzen und Ähnlichkeiten der beiden Sprachen notwendig, um eine ordnungsgemäße Portierung durchführen zu können. Aufgrund des kurz darauf folgenden Endes der Anstellung im Rahmen der Ingenieurspraxis war es leider nicht mehr möglich den portierten BCH-Code im Zusammenhang mit der gefundenen Lösung zur Optimierung der Grenzen einzusetzen.