668 lines
24 KiB
TeX
668 lines
24 KiB
TeX
\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<f64> {
|
|
(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<f64>} -- 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}
|