\documentclass[11pt]{article} \usepackage[margin=1in]{geometry} \usepackage{amsmath, amssymb} \usepackage{graphicx} \usepackage{listings} \usepackage{xcolor} \usepackage{hyperref} \usepackage{float} \usepackage{booktabs} \usepackage{caption} \usepackage{subcaption} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} % Rust code styling \definecolor{rustbg}{RGB}{248,248,248} \definecolor{rustcomment}{RGB}{128,128,128} \definecolor{rustkeyword}{RGB}{175,0,219} \definecolor{ruststring}{RGB}{163,21,21} \definecolor{rusttype}{RGB}{0,128,128} \lstdefinelanguage{Rust}{ keywords={fn, let, mut, pub, struct, impl, self, Self, use, mod, for, in, if, else, return, match, true, false, as, const, where, trait}, keywordstyle=\color{rustkeyword}\bfseries, ndkeywords={u64, u32, f64, bool, Vec, String, Option, Result, Box, usize, isize}, ndkeywordstyle=\color{rusttype}, comment=[l]{//}, morecomment=[s]{/*}{*/}, commentstyle=\color{rustcomment}\itshape, stringstyle=\color{ruststring}, morestring=[b]", sensitive=true, showstringspaces=false, basicstyle=\ttfamily\small, backgroundcolor=\color{rustbg}, frame=single, breaklines=true, tabsize=4, numbers=left, numberstyle=\tiny\color{gray}, numbersep=5pt, literate={≡}{{$\equiv$}}1, } \lstset{language=Rust} \title{Homework 1: Random Number Generators\\ \large ME 2243 Bayesian Signal Processing} \author{Dane Sabo} \date{\today} \begin{document} \maketitle %============================================================================== \section*{AI Use Statement} %============================================================================== This homework was completed with the assistance of \textbf{Claude Code} (Anthropic's CLI tool for Claude), using the \textbf{Claude Opus 4.5} model. \subsection*{How AI Was Used} \begin{itemize} \item \textbf{Learning Rust}: Claude helped explain Rust concepts like traits, ownership, and idiomatic patterns as I implemented the PRNGs. The explanations helped me understand \textit{why} certain approaches are preferred in Rust. \item \textbf{Debugging}: When code didn't compile or produced unexpected results (e.g., RANDU not showing planar structure, Rule 30 statistics collapsing), Claude helped diagnose issues and explain the underlying causes. \item \textbf{Plotting Code}: Claude wrote the \texttt{plotters} library boilerplate for generating histograms and 3D scatter plots, allowing me to focus on the PRNG algorithms themselves. \item \textbf{PCG32 Implementation}: As specified in Problem 3, Claude generated the PCG32 code from a prompt. I then annotated the code with my own comments explaining each section. \item \textbf{LaTeX Report}: Claude helped structure this report, filled in a lot of the fluff including code formatting and organization. \item \textbf{Mathematical Verification}: Claude suggested the mathematical verification of RANDU's planar constraint, demonstrating that $x_{n+2} \equiv 6x_{n+1} - 9x_n \pmod{2^{31}}$ holds for all triplets. \end{itemize} \subsection*{What I Did Myself} \begin{itemize} \item Implemented the LCG, LFSR (Xorshift), and Rule 30 algorithms based on the homework specifications \item Designed the code architecture (trait-based polymorphism) \item Annotated the PCG32 code with my own understanding \item All analysis sections in this report \end{itemize} \hrule \vspace{1em} %============================================================================== \section{Preamble: Implementation in Rust} %============================================================================== This homework was implemented in \textbf{Rust}, a systems programming language known for its memory safety, performance, and expressive type system. While the homework could have been completed in Python or MATLAB, I chose Rust as an opportunity to deepen my understanding of both the language and the underlying algorithms. \subsection{Why Rust?} Rust offers several advantages for implementing PRNGs: \begin{itemize} \item \textbf{Performance}: Rust compiles to native code, making bit operations and loops extremely fast \item \textbf{Type Safety}: The compiler catches many errors at compile time \item \textbf{Explicit Integer Types}: Rust has explicit \texttt{u32}, \texttt{u64} types, making bit-width considerations clear \item \textbf{No Hidden Overflow}: Rust requires explicit handling of integer overflow via \texttt{wrapping\_mul}, \texttt{wrapping\_add}, etc. \end{itemize} \subsection{Code Architecture} The code is organized as a Rust library with separate binary executables for each problem: \begin{verbatim} src/ lib.rs -- Trait definition and module exports lcg.rs -- Linear Congruential Generator lfsr.rs -- Linear Feedback Shift Register (Xorshift) pcg.rs -- Permuted Congruential Generator rule30.rs -- Rule 30 Cellular Automaton bin/ problem1.rs -- LCG analysis and comparison problem2.rs -- LFSR analysis problem3.rs -- PCG32 analysis problem4.rs -- Rule 30 analysis \end{verbatim} \subsection{The \texttt{RandomGenerator} Trait} A key feature of Rust is \textbf{traits}, which define shared behavior across types (similar to interfaces in other languages). All PRNGs implement a common trait: \begin{lstlisting} pub trait RandomGenerator { /// Generate the next random integer fn next(&mut self) -> u64; /// Return the modulus (range) of the generator fn modulus(&self) -> u64; // Default implementations provided for free: fn next_uniform(&mut self) -> f64 { self.next() as f64 / self.modulus() as f64 } fn generate_samples(&mut self, n: u64) -> Vec { (0..n).map(|_| self.next_uniform()).collect() } } \end{lstlisting} This means each PRNG only needs to implement \texttt{next()} and \texttt{modulus()}, and automatically receives \texttt{next\_uniform()} and \texttt{generate\_samples()} for free. This is Rust's approach to polymorphism---composition over inheritance. \subsection{Key Rust Syntax for Non-Rustaceans} For readers unfamiliar with Rust: \begin{itemize} \item \texttt{let mut x = 5;} -- Declare a mutable variable \item \texttt{\&self} -- Borrow (read-only reference to) self \item \texttt{\&mut self} -- Mutable borrow of self \item \texttt{x.wrapping\_mul(y)} -- Multiply with wraparound on overflow \item \texttt{x << n} -- Left bit shift by n positions \item \texttt{x >> n} -- Right bit shift by n positions \item \texttt{x \^{} y} -- XOR operation \item \texttt{Vec} -- A dynamic array (vector) of 64-bit floats \item \texttt{impl Trait for Type} -- Implement a trait for a specific type \end{itemize} %============================================================================== \section{Problem 1: Linear Congruential Generator} %============================================================================== \subsection{Part (a): LCG Implementation} The Linear Congruential Generator follows the recurrence relation: \begin{align} x_n &= (a \cdot x_{n-1} + c) \mod m \\ u_n &= x_n / m \end{align} \begin{lstlisting}[caption={LCG Implementation (src/lcg.rs)}] pub struct Lcg { state: u64, a: u64, // multiplier c: u64, // increment m: u64, // modulus } impl Lcg { pub fn new(seed: u64, a: u64, c: u64, m: u64) -> Self { Lcg { state: seed, a, c, m } } } impl RandomGenerator for Lcg { fn next(&mut self) -> u64 { self.state = (self.a.wrapping_mul(self.state) .wrapping_add(self.c)) % self.m; self.state } fn modulus(&self) -> u64 { self.m } } \end{lstlisting} The \texttt{wrapping\_mul} and \texttt{wrapping\_add} functions handle potential integer overflow by wrapping around, which is the correct behavior for modular arithmetic. \subsection{Part (b): Good LCG vs RANDU Comparison} Two LCGs were compared: \begin{itemize} \item \textbf{Good LCG}: $a = 1664525$, $c = 1013904223$, $m = 2^{31}$ \item \textbf{RANDU}: $a = 65539$, $c = 0$, $m = 2^{31}$ \end{itemize} \subsubsection{Statistical Results} \begin{table}[H] \centering \begin{tabular}{lcc} \toprule \textbf{Metric} & \textbf{Good LCG} & \textbf{RANDU} \\ \midrule Mean & 0.500304 & 0.502041 \\ Std Dev & 0.287667 & 0.287818 \\ Expected Mean & 0.5 & 0.5 \\ Expected Std Dev & 0.288675 & 0.288675 \\ \bottomrule \end{tabular} \caption{Statistical comparison of Good LCG and RANDU over 16,384 samples} \end{table} Both generators produce statistics very close to the expected values for a uniform distribution on $[0,1)$. The expected standard deviation is $\sqrt{1/12} \approx 0.2887$. \subsubsection{Histograms} \begin{figure}[H] \centering \begin{subfigure}[b]{0.48\textwidth} \includegraphics[width=\textwidth]{good_lcg_histogram.png} \caption{Good LCG} \end{subfigure} \hfill \begin{subfigure}[b]{0.48\textwidth} \includegraphics[width=\textwidth]{randu_histogram.png} \caption{RANDU} \end{subfigure} \caption{Histograms of Good LCG and RANDU. Both appear uniformly distributed.} \end{figure} \subsubsection{3D Scatter Plots} \begin{figure}[H] \centering \begin{subfigure}[b]{0.48\textwidth} \includegraphics[width=\textwidth]{good_lcg_3d.png} \caption{Good LCG -- uniform fill} \end{subfigure} \hfill \begin{subfigure}[b]{0.48\textwidth} \includegraphics[width=\textwidth]{randu_3d.png} \caption{RANDU -- visible planar structure} \end{subfigure} \caption{3D scatter plots of consecutive triplets $(u_n, u_{n+1}, u_{n+2})$} \end{figure} \subsubsection{Analysis} % TODO: DANE - Add your analysis here explaining: % 1. What do you observe in the 3D plots? % 2. Why does RANDU show planar structure while the good LCG fills space uniformly? % 3. What are the implications for Monte Carlo simulations? RANDU's failing is immediately clear from the 3D scatter plots. In the 3D plot, it's visible that RANDU generates random numbers with a higher-order connection. Any triplet of RANDU numbers will lie on one of 15 planes in the unit-cube. The consequence of this is that when RANDU is used for random number generation in tuples rather than single values, the tuples themselves are not truly random. \paragraph{Mathematical Verification (Claude Aside):} During development, the AI assistant suggested a mathematical verification of RANDU's planar structure. Every triplet of consecutive RANDU values satisfies the linear constraint: \[ x_{n+2} \equiv 6 \cdot x_{n+1} - 9 \cdot x_n \pmod{2^{31}} \] This was verified programmatically: \begin{verbatim} Triplet 0: x2=1769499, 6*x1-9*x0 (mod 2^31)=1769499, Match: true Triplet 1: x2=7077969, 6*x1-9*x0 (mod 2^31)=7077969, Match: true Triplet 2: x2=26542323, 6*x1-9*x0 (mod 2^31)=26542323, Match: true \end{verbatim} This algebraic constraint means that knowing any two consecutive values completely determines the third, which is why the points lie on planes defined by this linear equation. %============================================================================== \section{Problem 2: Linear Feedback Shift Register (Xorshift32)} %============================================================================== \subsection{Part (a): Xorshift32 Implementation} The Xorshift32 algorithm uses XOR and bit shift operations to generate pseudo-random numbers: \begin{align} x &\leftarrow x \oplus (x \ll a) \\ x &\leftarrow x \oplus (x \gg b) \\ x &\leftarrow x \oplus (x \ll c) \end{align} with parameters $a = 13$, $b = 17$, $c = 5$. \begin{lstlisting}[caption={LFSR/Xorshift Implementation (src/lfsr.rs)}] pub struct Lfsr { state: u32, a: u32, b: u32, c: u32, m: u32, } impl RandomGenerator for Lfsr { fn next(&mut self) -> u64 { self.state = self.state ^ (self.state << self.a); self.state = self.state ^ (self.state >> self.b); self.state = self.state ^ (self.state << self.c); self.state as u64 } fn modulus(&self) -> u64 { self.m as u64 } } \end{lstlisting} The three XOR-shift operations create complex bit mixing. Each operation combines the state with a shifted version of itself, creating pseudo-random patterns from deterministic operations. \subsection{Part (b): Statistical Analysis} \begin{table}[H] \centering \begin{tabular}{lcc} \toprule \textbf{Metric} & \textbf{Xorshift32} & \textbf{Expected} \\ \midrule Mean & 0.499617 & 0.5 \\ Std Dev & 0.288751 & 0.288675 \\ \bottomrule \end{tabular} \caption{Xorshift32 statistics over 100,000 samples} \end{table} \begin{figure}[H] \centering \begin{subfigure}[b]{0.48\textwidth} \includegraphics[width=\textwidth]{lfsr_histogram.png} \caption{Histogram} \end{subfigure} \hfill \begin{subfigure}[b]{0.48\textwidth} \includegraphics[width=\textwidth]{lfsr_3d.png} \caption{3D Scatter Plot} \end{subfigure} \caption{Xorshift32 analysis showing uniform distribution and no visible correlation structure} \end{figure} \subsubsection{Comparison to LCG} % TODO: DANE - Add your comparison here: % 1. How does the 3D scatter plot compare to the Good LCG and RANDU? % 2. What advantages does Xorshift have over LCG? % 3. Discuss speed vs. quality tradeoffs LFSR produces an indistinguishable result to the LCG random number generator with no clear bias. That being said, LFSR produced these random numbers significantly faster than the LCG generator. %============================================================================== \section{Problem 3: Permuted Congruential Generator (PCG32)} %============================================================================== \subsection{Part (a): AI-Generated Code with Annotations} Per the homework instructions, the following code was generated using Claude Code with the prompt: \textit{``Write code to implement PCG32 in my favorite programming language (Rust!)''} The annotations (comments starting with \texttt{// DAS}) are my own explanations of what each part of the code does. \begin{lstlisting}[caption={PCG32 Implementation with Annotations (src/pcg.rs)}] // DAS COMMENT: // The prompt for creating this code was verbatim: "Write code to // implement PCG32 in my favorite programming language (Rust!)" // This was generated using a Claude Code session. pub struct Pcg32 { state: u64, // Internal LCG state (64-bit) increment: u64, // Must be odd } impl Pcg32 { // Standard PCG32 parameters const MULTIPLIER: u64 = 6364136223846793005; const DEFAULT_INCREMENT: u64 = 1442695040888963407; pub fn new(seed: u64) -> Self { // DAS - Any new instance immediately runs through // new_with_increment with default increment Pcg32::new_with_increment(seed, Self::DEFAULT_INCREMENT) } pub fn new_with_increment(seed: u64, increment: u64) -> Self { let mut pcg = Pcg32 { state: 0, increment: (increment << 1) | 1, // DAS - Ensures increment is always odd by: // 1. Shifting left by 1 (multiplies by 2, making LSB=0) // 2. OR with 1 (sets LSB=1, guaranteeing odd) }; // DAS - This is the initialization sequence. // Claude skipped step 3 from the homework (the first LCG step // before adding seed). It adds seed to state, then steps. pcg.state = pcg.state.wrapping_add(seed); pcg.step(); pcg } fn step(&mut self) { // DAS - This is a standard LCG step: // state = state * multiplier + increment self.state = self.state .wrapping_mul(Self::MULTIPLIER) .wrapping_add(self.increment); } fn output(state: u64) -> u32 { // XOR high and low parts let xorshifted = (((state >> 18) ^ state) >> 27) as u32; let rot = (state >> 59) as u32; // DAS - The homework says "Return (xorshifted >> rot)" but // that would pad zeros, not truly rotate. Claude correctly // uses rotate_right() for a proper bit rotation. xorshifted.rotate_right(rot) } } impl RandomGenerator for Pcg32 { fn next(&mut self) -> u64 { let old_state = self.state; self.step(); // DAS - This matches the homework exactly: // 1. Save old state // 2. Advance state (LCG step) // 3. Output permutation of OLD state Self::output(old_state) as u64 } fn modulus(&self) -> u64 { u32::MAX as u64 } } \end{lstlisting} \subsection{Understanding PCG32} PCG32 combines two concepts: \begin{enumerate} \item \textbf{LCG for state advancement}: The internal 64-bit state advances using a standard LCG with a carefully chosen multiplier. This provides the period ($2^{64}$) and determines the sequence. \item \textbf{Output permutation (XSH-RR)}: The output is derived by applying a permutation function to the state: \begin{itemize} \item \textbf{XSH} (Xorshift High): \texttt{((state >> 18) \^{} state) >> 27} mixes the high bits with the low bits \item \textbf{RR} (Random Rotation): The result is rotated by an amount determined by the high 5 bits of the state \end{itemize} \end{enumerate} The key insight is that while the LCG state has predictable low bits (they cycle with small periods), the permutation function extracts randomness from the high-quality high bits while using the state itself to determine how to scramble the output. \subsection{Statistical Results} \begin{table}[H] \centering \begin{tabular}{lcc} \toprule \textbf{Metric} & \textbf{PCG32} & \textbf{Expected} \\ \midrule Mean & 0.498255 & 0.5 \\ Std Dev & 0.288584 & 0.288675 \\ \bottomrule \end{tabular} \caption{PCG32 statistics over 100,000 samples} \end{table} \begin{figure}[H] \centering \begin{subfigure}[b]{0.48\textwidth} \includegraphics[width=\textwidth]{pcg32_histogram.png} \caption{Histogram} \end{subfigure} \hfill \begin{subfigure}[b]{0.48\textwidth} \includegraphics[width=\textwidth]{pcg32_3d.png} \caption{3D Scatter Plot} \end{subfigure} \caption{PCG32 analysis showing excellent statistical properties (<- Claude is proud of itself)} \end{figure} %============================================================================== \section{Problem 4: Rule 30 Cellular Automaton} %============================================================================== \subsection{Part (a): Rule 30 Implementation} Rule 30 is an elementary cellular automaton where each cell's next state depends on its current state and its two neighbors: \begin{table}[H] \centering \begin{tabular}{c|cccccccc} \textbf{Configuration} & 111 & 110 & 101 & 100 & 011 & 010 & 001 & 000 \\ \hline \textbf{New State} & 0 & 0 & 0 & 1 & 1 & 1 & 1 & 0 \\ \end{tabular} \caption{Rule 30 transition table (binary: 00011110 = 30 in decimal)} \end{table} \begin{lstlisting}[caption={Rule 30 Implementation (src/rule30.rs)}] pub struct Rule30 { state: u32, // 32 cells packed into a single u32 } impl Rule30 { pub fn new(seed: u32) -> Self { // Initialize with seed; if 0, use single bit in center let initial_state = if seed == 0 { 1 << 16 } else { seed }; Rule30 { state: initial_state } } // Apply Rule 30 with periodic boundary conditions // Uses bitwise operations on all 32 cells simultaneously fn step(&mut self) { // For Rule 30: new_cell = left XOR (center OR right) let left = self.state.rotate_right(1); // Wrap right let center = self.state; let right = self.state.rotate_left(1); // Wrap left // Rule 30: output = left XOR (center OR right) self.state = left ^ (center | right); } } impl RandomGenerator for Rule30 { fn next(&mut self) -> u64 { self.step(); // Advance one generation self.state as u64 // Entire 32-bit state IS the number } fn modulus(&self) -> u64 { (u32::MAX as u64) + 1 // 2^32 } } \end{lstlisting} \textbf{Key Implementation Details:} \begin{itemize} \item \textbf{32 cells} as specified in the homework, packed into a single \texttt{u32} \item \textbf{Periodic boundary conditions} via \texttt{rotate\_left/right}, creating a circular topology \item \textbf{Bitwise operations} apply Rule 30 to all 32 cells simultaneously---extremely fast! \item \textbf{Output method}: Each generation, the entire 32-bit state is read as the random number, matching the homework formula: $u = \sum_{b=1}^{32} s_b \cdot 2^{-b}$ \end{itemize} The elegant insight is that Rule 30's update formula $\text{new} = \text{left} \oplus (\text{center} \lor \text{right})$ can be applied to all 32 cells in parallel using bitwise operations. \subsection{Part (b): Statistical Analysis} \begin{table}[H] \centering \begin{tabular}{lcc} \toprule \textbf{Metric} & \textbf{Rule 30} & \textbf{Expected} \\ \midrule Mean & 0.500579 & 0.5 \\ Std Dev & 0.289578 & 0.288675 \\ \bottomrule \end{tabular} \caption{Rule 30 statistics over 100,000 samples (32-cell implementation)} \end{table} \begin{figure}[H] \centering \begin{subfigure}[b]{0.48\textwidth} \includegraphics[width=\textwidth]{rule30_histogram.png} \caption{Histogram} \end{subfigure} \hfill \begin{subfigure}[b]{0.48\textwidth} \includegraphics[width=\textwidth]{rule30_3d.png} \caption{3D Scatter Plot} \end{subfigure} \caption{Rule 30 analysis showing good statistical properties} \end{figure} \subsubsection{Comparison to LCG and LFSR} % TODO: DANE - Add your comparison here: % 1. How does Rule 30's 3D plot compare to the other generators? % 2. What are the tradeoffs of using a cellular automaton vs. algebraic methods? % 3. Why did Wolfram choose Rule 30 for Mathematica's random number generator? % 4. Discuss computational cost vs. randomness quality Rule30 can be extremely fast when implemented on something like a gate array. Because each cell is implemented as an OR and XOR operation with its neighbors, it is a embarrassingly parallel computation. Rule30 produces seemingly random numbers, but the 3D scatterplot shows that there are some problems with Rule30 similar to RANDU, or at least my implementation of Rule30 does. There is not even coverage of the space. %============================================================================== \section{Summary and Conclusions} %============================================================================== Listed is a summary of the performance of each PRNG. \begin{table}[H] \centering \begin{tabular}{lcccl} \toprule \textbf{Generator} & \textbf{Mean} & \textbf{Std Dev} & \textbf{3D Structure} & \textbf{Notes} \\ \midrule Good LCG & 0.500 & 0.288 & Uniform & Simple, fast \\ RANDU & 0.502 & 0.288 & \textbf{15 planes!} & Catastrophically flawed \\ Xorshift32 & 0.500 & 0.289 & Uniform & Fast, good quality \\ PCG32 & 0.498 & 0.289 & Uniform & Modern, excellent quality \\ Rule 30 & 0.501 & 0.290 & Uniform & Fast (bitwise on u32) \\ \bottomrule \end{tabular} \caption{Summary comparison of all implemented PRNGs} \end{table} %============================================================================== \newpage \appendix \section{Complete Code Listings} %============================================================================== \subsection{lib.rs -- Main Library with Trait Definition} \lstinputlisting[language=Rust]{src/lib.rs} \subsection{lcg.rs -- Linear Congruential Generator} \lstinputlisting[language=Rust]{src/lcg.rs} \subsection{lfsr.rs -- Linear Feedback Shift Register} \lstinputlisting[language=Rust]{src/lfsr.rs} \subsection{pcg.rs -- Permuted Congruential Generator} \lstinputlisting[language=Rust]{src/pcg.rs} \subsection{rule30.rs -- Rule 30 Cellular Automaton} \lstinputlisting[language=Rust]{src/rule30.rs} \subsection{problem1.rs -- LCG Analysis Program} \lstinputlisting[language=Rust]{src/bin/problem1.rs} \subsection{problem2.rs -- LFSR Analysis Program} \lstinputlisting[language=Rust]{src/bin/problem2.rs} \subsection{problem3.rs -- PCG32 Analysis Program} \lstinputlisting[language=Rust]{src/bin/problem3.rs} \subsection{problem4.rs -- Rule 30 Analysis Program} \lstinputlisting[language=Rust]{src/bin/problem4.rs} \end{document}