Methoden der Versuchsplanung: Optimal Design und Sobol-Sequenzen
Methoden der Versuchsplanung: Optimal Design und Sobol-Sequenzen
Author: Christoph Würsch, ICE, Eastern Switzerland University of Applied Sciences, OST
Dieser Blog behandelt fundamentale Methoden der Versuchsplanung (Design of Experiments, DoE) und stellt zwei primäre Ansätze gegenüber: die modellbasierte optimale Versuchsplanung und modellunabhängige, raumfüllende Sampling-Strategien.
- Der erste Teil des Dokuments führt in die optimale Versuchsplanung ein, die darauf abzielt, einen Versuchsplan für ein spezifisches statistisches Modell (z.B. Polynomregression) zu optimieren. Der Fokus liegt auf der Informationsmatrix und den davon abgeleiteten Optimalitätskriterien (D-, A-, I- und G-Optimalität), die jeweils unterschiedliche Aspekte der Modellgüte, wie die Parametervarianz oder die Vorhersagevarianz, optimieren.
- Der zweite Teil vergleicht drei zentrale raumfüllende Sampling-Methoden für die Erstellung von Surrogatmodellen, wenn kein a-priori-Modell angenommen wird. Es werden das einfache Monte-Carlo-Sampling (MC), das Latin Hypercube Sampling (LHS) und Quasi-Monte-Carlo-Methoden (QMC) am Beispiel der Sobol-Sequenz detailliert. Der Vergleich hebt die Unterschiede in Uniformität, Konvergenzrate ( für MC vs. für QMC) und Erweiterbarkeit hervor, wobei die Sobol-Sequenz die beste mehrdimensionale Raumabdeckung und Flexibilität bietet.
- Der dritte Teil, “Sobol-Sequenzen ‘from scratch’”, bietet eine detaillierte technische Implementierung der Sobol-Sequenz. Es wird der Bratley-und-Fox-Algorithmus erklärt, der iterativ Sobol-Punkte generiert. Dieser Ansatz basiert auf primitiven Polynomen über dem Körper , der Vorkalkulation von Richtungszahlen (Direction Numbers) und der effizienten Anwendung von bitweisen XOR-Operationen, um eine hochgradig uniforme Punktmenge zu erzeugen.
Inhaltsverzeichnis
- Optimale Versuchsplanung (Optimal Design)
- Sampling-Methoden für die Versuchsplanung (DoE)
- Sobol-Sequenzen ”from scratch”
- Algorithmus (Bratley & Fox)
1 Optimale Versuchsplanung (Optimal Design)
Im Gegensatz zu raumfüllenden Versuchsplänen (wie Latin Hypercube oder Sobol-Sequenzen), die modellunabhängig den gesamten Raum abtasten, ist die optimale Versuchsplanung (Optimal Design) modellbasiert. Das Ziel ist es, einen Versuchsplan zu konstruieren, der ”optimal” für die Anpassung (Fitting) eines spezifischen statistischen Modells ist, typischerweise eines Polynommodells (z.B. linear, quadratisch oder mit Interaktionen).
1.1 Die Informationsmatrix
Der Kern der optimalen Versuchsplanung ist die Informationsmatrix . Für ein Standard-Regressionsmodell , wobei die Versuchsplanmatrix (Designmatrix) ist, ist die Informationsmatrix definiert als:
Die Inverse dieser Matrix, , ist proportional zur Varianz-Kovarianz-Matrix der geschätzten Modellparameter .
Ein ”optimaler” Plan ist einer, der eine skalare Funktion dieser Matrix (oder ihrer Inversen) maximiert oder minimiert. Die verschiedenen Kriterien (D, A, I, G) definieren, welche Funktion optimiert wird.
1.2 Optimalitätskriterien
Es gibt mehrere gängige Kriterien, um die ”Güte” eines Versuchsplans basierend auf der Informationsmatrix zu bewerten.
D-Optimalität
Ein D-optimaler Plan maximiert die Determinante der Informationsmatrix:
Dies ist äquivalent zur Minimierung der Determinante der Kovarianzmatrix . Geometrisch bedeutet dies, dass das Volumen des Konfidenzellipsoids für die Modellparameter minimiert wird. D-Optimalität ist das am häufigsten verwendete Kriterium.
A-Optimalität
Ein A-optimaler Plan minimiert die Spur (Summe der Diagonalelemente) der inversen Informationsmatrix:
Da die Diagonalelemente der Kovarianzmatrix entsprechen, minimiert die A-Optimalität die durchschnittliche Varianz der Parameterschätzungen .
I-Optimalität (oder V-Optimalität)
Ein I-optimaler Plan minimiert die durchschnittliche Vorhersagevarianz über den gesamten Design-Space :
Dieses Kriterium ist ideal, wenn das Hauptziel die präzise Vorhersage (Interpolation) innerhalb des Versuchsraums ist, und nicht notwendigerweise die präziseste Schätzung der einzelnen Parameter .
G-Optimalität
Ein G-optimaler Plan folgt einem Minimax-Ansatz. Er minimiert die maximale Vorhersagevarianz im Design-Space :
Dieser Ansatz ist sehr robust, da er darauf abzielt, den ”Worst-Case” (den Punkt im Raum mit der höchsten Vorhersageunsicherheit) zu kontrollieren und zu minimieren.
1.3 Modellbasiert vs. Raumfüllend
-
Optimale Pläne (D, A, G, I): Werden verwendet, wenn ein spezifisches Regressionsmodell (z.B. ) bekannt ist und effizient gefittet werden soll. Sie sind ideal für die Parameter-Screening und die Optimierung von Antwortflächen (Response Surface Methodology).
-
Raumfüllende Pläne (LHS, Sobol): Werden verwendet, wenn kein Modell a priori angenommen wird. Sie sind ideal für die allgemeine Exploration, die Erstellung von Surrogatmodellen (z.B. Gauss-Prozesse, Neuronale Netze) oder für Sensitivitätsanalysen.
2. Sampling-Methoden für die Versuchsplanung (DoE)
Die Erstellung eines Surrogatmodells (oder Metamodells) hat zum Ziel, eine rechenintensive ”Black-Box”-Funktion durch ein rechengünstiges Näherungsmodell zu ersetzen. Diese Funktion ist oft das Ergebnis einer komplexen Simulation (z.B. FEM, CFD). Sei unser Eingaberaum (Design-Space) , wobei die Anzahl der Dimensionen (Design-Parameter) ist. Für die Analyse normieren wir diesen Raum typischerweise auf den -dimensionalen Einheits-Hyperwürfel .
Ein Versuchsplan (Design of Experiments, DoE) ist eine Menge von Stützpunkten , an denen die teure Funktion ausgewertet wird. Das Ziel ist, diese Punkte so zu wählen, dass sie den Raum ”optimal” abdecken (engl. space-filling), um ein möglichst genaues Surrogatmodell zu trainieren.
”Optimal” bedeutet hierbei vor allem:
- Uniformität: Die Punkte sollen den Raum gleichmässig füllen, ohne Cluster (Ballungen) oder grosse Lücken (Lücken).
- Orthogonalität: Die Punkte sollten möglichst unkorreliert sein, um eine Verwechslung der Einflüsse verschiedener Parameter zu vermeiden.
Im Folgenden werden drei fundamentale Sampling-Strategien mathematisch und konzeptionell detailliert.
2.1 Pseudo-Zufalls-Sampling (Monte Carlo, MC)
Das Monte-Carlo-Sampling (MC) ist die fundamentalste stochastische Methode. Ein Versuchsplan mit Punkten in Dimensionen wird generiert, indem unabhängige Zufallszahlen aus der stetigen Gleichverteilung gezogen werden.
Jede Koordinate jedes Punktes ist unabhängig von allen anderen:
Stellen Sie sich vor, Sie werfen eine Handvoll Sand (die Punkte) auf eine quadratische Platte (den 2D-Design-Space). Die Körner landen völlig zufällig. Es ist unvermeidlich, dass einige Bereiche dicht bedeckt sind (Cluster) und andere Bereiche fast leer bleiben (Lücken). Das zentrale Gütemass für die Gleichverteilung ist die Diskrepanz . Sie misst die maximale Abweichung zwischen dem ”Volumen” eines beliebigen Teilquaders (gemessen an der Anzahl der Punkte, die in ihn fallen) und seinem tatsächlichen geometrischen Volumen.
Für MC-Methoden ist der erwartete Fehler bei der numerischen Integration (ein verwandtes Problem) durch die Koksma-Hlawka-Ungleichung beschränkt. In der Praxis konvergiert der probabilistische Fehler der MC-Integration mit einer Rate von:
Diese Konvergenzrate ist (bemerkenswerterweise) unabhängig von der Dimension , aber sie ist sehr langsam. Um den Fehler zu halbieren, muss die Anzahl der Punkte vervierfacht werden.
- Vorteile: Extrem einfach zu implementieren, probabilistisch unvoreingenommen (unbiased), trivial erweiterbar (neue Punkte können hinzugefügt werden, ohne die Statistik zu verletzen).
- Nachteile: Sehr ineffizient. Erzeugt unvermeidlich Cluster und Lücken. Für eine gute Raumabdeckung ist ein sehr grosses erforderlich.
2.2 Latin Hypercube Sampling (LHS)
LHS ist eine stratifizierte (geschichtete) Zufallsstichprobe, die das Clustering-Problem von MC in den 1D-Projektionen löst. Die Generierung eines LHS-Plans mit Punkten in Dimensionen folgt drei Schritten:
-
Stratifizierung: Jede der Dimensionen wird in gleichwahrscheinliche Intervalle (Strata) unterteilt: für .
-
Permutation: Für jede Dimension wird eine zufällige Permutation (Umsortierung) der Zahlen erstellt.
-
Generierung: Der -te Sample-Punkt wird nun konstruiert. Die -te Koordinate des -ten Punktes () wird generiert, indem ein zufälliger Punkt aus dem -ten Intervall der -ten Dimension gezogen wird.
Stellen Sie sich ein Sudoku-Gitter (für 2D) oder Schachu-Türme auf einem Brett vor. Das Ziel ist es, die Türme so zu platzieren, dass sich keine zwei Türme bedrohen. Das Ergebnis ist, dass in jeder Zeile und jeder Spalte genau ein Turm steht.
LHS stellt sicher, dass, wenn man den -dimensionalen Raum auf eine beliebige einzelne Achse (Dimension) projiziert, genau ein Punkt in jedes der Intervalle fällt. LHS garantiert eine perfekte, gleichmässige Abdeckung in allen 1D-Projektionen. Dies ist ein enormer Vorteil gegenüber MC.
- Vorteile: Deutlich bessere Raumfüllung als MC. Erzwingt die Abtastung der Extrembereiche (der ”Ränder” des Design-Space). Reduziert die Varianz bei der Schätzung von Modell-Outputs.
- Nachteile: Garantiert keine gute mehrdimensionale Uniformität. Die Punkte können sich immer noch auf Hyper-Diagonalen ansammeln, was zu unerwünschten Korrelationen im Versuchsplan führt. Die Erweiterbarkeit ist nicht gegeben; das Hinzufügen von neuen Punkten zu einem -Punkte-Plan ergibt keinen validen -Punkte-LHS-Plan.
2.3 Quasi-Monte-Carlo (QMC) / Sobol-Sequenz
QMC-Methoden verwenden deterministische Sequenzen mit geringer Diskrepanz (Low-Discrepancy Sequences, LDS), um die Nachteile von MC zu überwinden. Die Sobol-Sequenz ist eine der bekanntesten digitalen -Sequenzen. Die Sobol-Sequenz ist keine Zufallsstichprobe, sondern eine hochgradig strukturierte, unendliche Sequenz von Punkten. Ihre Konstruktion basiert auf der Arithmetik im endlichen Körper (wobei Addition bitweises XOR ist).
-
Richtungszahlen: Für jede Dimension (bis ) wird ein Satz von Richtungszahlen aus primitiven Polynomen über generiert.
-
Digitale Konstruktion: Um den -ten Punkt zu finden, wird der Index binär dargestellt: .
-
XOR-Operation: Die -te Koordinate wird durch eine bitweise XOR-Summe der Richtungszahlen gebildet, die den ‘1’-Bits in entsprechen:
(Der im Python-Code gezeigte Algorithmus ist eine schnellere, iterative Variante hiervon, die aus berechnet.)
Stellen Sie sich das systematische Kacheln eines Badezimmerbodens vor. Sie werfen die Kacheln nicht zufällig (MC). Sie legen sie auch nicht nur in sauberen Reihen (was zu Korrelationen führt). Sie verwenden ein komplexes, deterministisches, aber nicht-periodisch wirkendes Muster (ähnlich einer Penrose-Parkettierung), das darauf ausgelegt ist, niemals Lücken zu hinterlassen. Jeder neue Punkt (Kachel) wird gezielt in die grösste verbleibende Lücke gelegt.
QMC-Sequenzen sind explizit darauf ausgelegt, die Diskrepanz zu minimieren. Der Fehler bei der numerischen Integration konvergiert für QMC theoretisch mit:
Diese Konvergenz ist deutlich schneller als von MC, solange die Dimension nicht zu gross ist (der Faktor ist der ”Fluch der Dimensionalität” für QMC).
- Vorteile: Beste Uniformität und Raumfüllung im mehrdimensionalen Raum. Schnellste Konvergenzrate für Integration (und oft beste Surrogatmodell-Güte) bei niedrigen bis moderaten Dimensionen.
- Erweiterbarkeit (Extensibility): Ein herausragender Vorteil. Eine Sobol-Sequenz ist unendlich. Man kann Punkte berechnen, das Modell testen und bei Bedarf weitere Punkte hinzufügen. Die resultierende Menge von 200 Punkten behält die Eigenschaft der geringen Diskrepanz bei.
- Nachteile: Deterministisch. Könnte theoretisch mit einer periodischen Zielfunktion ”kollidieren”. Der theoretische Vorteil nimmt in sehr hohen Dimensionen () ab.
2.4 Tabellarischer Vergleich
Tabelle 1: Mathematischer und praktischer Vergleich der Sampling-Methoden
| Eigenschaft | Monte Carlo (MC) | Latin Hypercube (LHS) | Sobol-Sequenz (QMC) |
|---|---|---|---|
| Typ | Pseudo-Zufällig | Stratifiziert-Zufällig | Deterministisch (Quasi-Zufall) |
| Ziel | Probabilistische Abdeckung | Gleichverteilung der 1D-Projektionen | Minimierung der -dim. Diskrepanz |
| Raumfüllung | Gering (Cluster/Lücken) | Mittel (Keine 1D-Cluster) | Sehr Hoch (Beste Uniformität) |
| Erweiterbarkeit | Ja (trivial) | Nein (Neugenerierung) | Ja (Haupteigenschaft) |
| Konvergenzrate (Integrationsfehler) | (langsam, -unabhängig) | Besser als MC, variabel | (sehr schnell bei kleinem ) |
| Ideal für | Robuste Tests, sehr hohe Dimensionen () | Standard-DoE, wenn feststeht, UQ | Surrogatmodell-Training, Sensitivitätsanalyse (Sobol-Ind.) |
3. Sobol-Sequenzen ”from scratch”
Eine Sobol-Sequenz ist eine sogenannte Quasi-Zufalls-Sequenz (auch low-discrepancy sequence oder Sequenz mit geringer Diskrepanz genannt). Im Gegensatz zu Pseudo-Zufallszahlen (PRNGs), wie sie in Standard-Monte-Carlo-Simulationen (MC) verwendet werden, zielt eine Quasi-Zufalls-Sequenz nicht darauf ab, statistischen Zufall zu imitieren. Das Hauptziel einer Sobol-Sequenz ist es, einen -dimensionalen Raum (oft den Einheits-Hyperwürfel ) so gleichmässig wie möglich abzudecken.
- Pseudo-Zufall (Monte Carlo): Erzeugt tendenziell Lücken (areas of under-sampling) und Cluster (areas of over-sampling), besonders bei geringer Punktzahl.
- Quasi-Zufall (Sobol): Ist deterministisch so konstruiert, dass jeder neue Punkt gezielt in die grösste existierende Lücke ”fällt”.
Diese Eigenschaft der gleichmässigen Abdeckung führt zu einer signifikant schnelleren Konvergenz bei numerischen Integrationen (Quasi-Monte-Carlo, QMC) und einer effizienteren Abtastung von Design-Spaces im Design of Experiments (DoE). Die Sobol-Sequenz ist eine digitale Sequenz (Basis 2). Ihre Konstruktion basiert auf der Arithmetik im endlichen Körper (Galois-Feld) , der nur aus den Elementen besteht, wobei die Addition dem bitweisen XOR () entspricht.
3.1 Primitive Polynome
Die Grundlage für jede Dimension der Sobol-Sequenz ist ein sorgfältig ausgewähltes primitives Polynom über . Ein solches Polynom vom Grad hat die Form:
wobei alle Koeffizienten entweder 0 oder 1 sind (). Diese Polynome sind die ”Generatoren” für die Sequenz in dieser Dimension.
3.2 Richtungszahlen (Direction Numbers)
Aus jedem primitiven Polynom wird ein Satz von Richtungszahlen abgeleitet. Dies sind -Bit-Integer (in der Python-Implementierung ), die als die ”magischen Konstanten” des Algorithmus dienen.
Die Richtungszahlen werden über eine Rekurrenzrelation generiert, die direkt vom Polynom abhängt:
-
Initialwerte: Die ersten Richtungszahlen werden als ungerade ganze Zahlen gewählt. Diese sind die Konstanten, die im Python-Code als
SOBOL_INIT_Mgespeichert sind. -
Rekurrenzrelation: Für wird über eine XOR-basierte Rekurrenz berechnet, die die Koeffizienten des Polynoms nutzt:
In der Praxis (und im Python-Code) werden diese als -Bit-Integer gespeichert, die bereits an die richtige Bit-Position verschoben (left-shifted) sind, um die Division durch am Ende zu vereinfachen.
4. Algorithmus (Bratley & Fox)
Der Algorithmus von Bratley & Fox ist eine hocheffiziente Methode zur Erzeugung von Sobol-Punkten. Er ist deterministisch und basiert vollständig auf bitweisen Operationen (XOR, Bit-Shifts).
- Stufe 1 (Setup): Nutzt primitive Polynome und eine XOR-Rekurrenz, um die ”magischen” Richtungszahlen für jede Dimension zu berechnen.
- Stufe 2 (Iteration): Nutzt den Index der rechtesten Null () von , um aus und der einzelnen Richtungszahl zu berechnen.
Das Ergebnis ist eine Sequenz, die den -dimensionalen Raum hervorragend gleichmässig abdeckt und sich ideal für QMC-Methoden und die Exploration von Design-Spaces eignet. Der folgende Python-Code verwendet nicht die naive Implementierung (die den Index direkt mit allen Richtungszahlen per XOR verknüpft). Stattdessen nutzt er einen schnellen iterativen Algorithmus (basierend auf Bratley und Fox, ACM Trans. Math. Softw. 659), der den -ten Punkt sehr effizient aus dem vorherigen Punkt berechnet. Der Algorithmus besteht aus zwei Stufen.
4.1 Stufe 1: Vorkalkulation der Richtungszahlen (V)
Bevor die Sequenz generiert wird, berechnet der Code ein 2D-Array . Dies ist die -te -Bit-Richtungszahl für die -te Dimension.
-
Initialwerte (): Die ersten Werte (aus
SOBOL_INIT_M) werden geladen und an die korrekte Bit-Position (ganz links) verschoben. -
Rekurrenz (): Die restlichen werden mit der bit-verschobenen Version der Rekurrenzrelation (siehe oben) berechnet. Der Python-Code implementiert dies als:
(Wobei bedeutet: , falls , und , falls . Dies wird im Code durch die Bit-Maskierung von
temp_aerreicht.)

4.2 Stufe 2: Generierung der Sequenzpunkte
Der Kern des schnellen Algorithmus ist die iterative Erzeugung des -ten Punktes .
Wir verwalten einen -dimensionalen Vektor von -Bit-Integern, , der den ”Zustand” der Sequenz darstellt. Der normalisierte Punkt ist einfach .
-
Initialisierung: Der nullte Punkt ist der Ursprung.
-
Iteration (für ): Um aus zu erhalten, wird eine einzige Richtungszahl pro Dimension verwendet.
-
Finden des Index : Der entscheidende Schritt ist die Bestimmung des Index . Dieser Index ist definiert als:
Der Index des rechtesten Null-Bits (rightmost zero-bit) in der Binärdarstellung von .
Beispiele:
- : (0b000). Rechteste 0 ist bei Index .
- : (0b001). Rechteste 0 ist bei Index .
- : (0b010). Rechteste 0 ist bei Index .
- : (0b011). Rechteste 0 ist bei Index .
- : (0b100). Rechteste 0 ist bei Index .
(Der Python-Code findet durch die
while (k & 1)-Schleife.) -
Anwendung des XOR: Der -te Punkt wird nun für jede Dimension separat berechnet, wobei derselbe Index verwendet wird:
Der Unterschied zwischen den Dimensionen entsteht also, weil (das Array der Richtungszahlen) für jede Dimension einzigartig ist.
-
Normalisierung: Der finale Punkt für die Ausgabe wird durch Normalisierung gewonnen:

4.3 Python Implementierung (Bratley & Fox)
import numpy as np
# --- KONSTANTEN (Die ''Magischen Zahlen'' von Joe & Kuo, bis dim 6) ---
# Diese Daten sind die ''Black Box'', die aus der mathematischen Theorie stammt.
# new-joe-kuo-6-21201.txt
# d = Grad des primitiven Polynoms
SOBOL_DEGREES = [1, 2, 3, 3, 4, 4]
# a = Koeffizienten des Polynoms (als einzelne Ganzzahl kodiert)
SOBOL_COEFFS = [0, 1, 1, 3, 1, 3]
# m = Die initialen Richtungszahlen (m_1, ..., m_d)
SOBOL_INIT_M = [
[1],
[1, 3],
[1, 3, 1],
[1, 1, 1],
[1, 1, 3, 3],
[1, 3, 5, 13],
]
# Maximale Anzahl an Bits (bestimmt die Praezision und Periodizitaet)
MAX_BITS = 32
# Normalisierungsfaktor (um von Integer auf [0, 1) zu kommen)
NORMALIZER = 1.0 / (2**MAX_BITS)
def sobol_sequence_from_scratch(num_points: int, dimensions: int) -> np.ndarray:
''''''
Generiert eine Sobol-Sequenz ''from scratch'' unter expliziter
Verwendung von bitweisen Operationen und Richtungszahlen.
Basiert auf den Daten von S. Joe und F. Y. Kuo.
''''''
if dimensions > len(SOBOL_DEGREES):
raise ValueError(
f''Maximale Dimension {len(SOBOL_DEGREES)} ueberschritten. ''
''Fuer mehr Dimensionen muessen die Konstanten erweitert werden.''
)
# --- 1. VORBEREITUNG: GENERIERE DIE RICHTUNGSZAHLEN (V) ---
# V[j][i] ist die i-te Richtungszahl fuer die j-te Dimension.
# Wir berechnen sie aus den Konstanten (Polynomen).
V = np.zeros((dimensions, MAX_BITS), dtype=np.uint64)
for j in range(dimensions):
d = SOBOL_DEGREES[j]
a = SOBOL_COEFFS[j]
m = SOBOL_INIT_M[j]
# 1a. Setze die initialen Werte (m_1 ... m_d)
# Wir muessen sie an die richtige Bit-Position schieben
for i in range(d):
# m_i wird zu V[j][i], nach links geshiftet
V[j, i] = np.uint64(m[i]) << (MAX_BITS - 1 - i)
# 1b. Generiere die restlichen V's (i > d) ueber die Rekurrenzrelation
# V_i = a_1 V_{i-1} ^ a_2 V_{i-2} ^ ... ^ a_d V_{i-d} ^ (V_{i-d} >> d)
for i in range(d, MAX_BITS):
# V_{i-d} ^ (V_{i-d} >> d)
V[j, i] = V[j, i - d] ^ (V[j, i - d] >> d)
# Wende die Koeffizienten 'a' an
temp_a = a
for k in range(d - 1):
# Wenn das k-te Bit von 'a' gesetzt ist, XOR anwenden
if (temp_a & 1):
V[j, i] = V[j, i] ^ V[j, i - (d - 1) + k]
temp_a >>= 1
# --- 2. GENERIERUNG DER SEQUENZ ---
# `X` speichert den aktuellen Punkt als Integer-Wert
X = np.zeros(dimensions, dtype=np.uint64)
# `points` speichert die finale Sequenz (normalisierte Floats)
points = np.zeros((num_points, dimensions))
# Der erste Punkt (i=0) ist immer der Ursprung [0, 0, ...]
# (bleibt bei points[0] als Nullen stehen)
for i in range(1, num_points):
# 2a. Finde den Index 'c' des rechtesten Null-Bits in (i-1)
# Beispiel: i=1 -> (i-1)=0 (0b000) -> c=0
# i=2 -> (i-1)=1 (0b001) -> c=1
# i=3 -> (i-1)=2 (0b010) -> c=0
# i=4 -> (i-1)=3 (0b011) -> c=2
c = 0
k = i - 1 # (i-1) als Binaerzahl betrachten
while (k & 1):
k >>= 1
c += 1
if c >= MAX_BITS:
# Sollte bei normaler Nutzung nicht passieren
print(f''Warnung: MAX_BITS ({MAX_BITS}) erreicht bei Punkt {i}'')
continue
# 2b. EXPLIZITE OPERATION: Generiere den neuen Punkt X_i
# X_i = X_{i-1} XOR V[c]
# (Wir wenden dies auf jede Dimension an)
for j in range(dimensions):
X[j] = X[j] ^ V[j, c]
# 2c. Speichere den normalisierten Punkt
# (Integer-Wert geteilt durch 2^MAX_BITS)
points[i, :] = X * NORMALIZER
return points