Assignment 1

Table of Contents

In this assignment we'll be looking at two classic mathematical conjectures, one of which is unsolved and one of which has been disproved, and both of which have a history of people trying to verify them with computers. The goal of this assignment is to demonstrate that it's quite easy to write and optimize fast math verification code in Rust (no more rooting around in C or C++). Generally speaking, I'll try to design assignments in this way: if you just want a bit of practice, that's all you need to do, but if you want a challege, it'll be there too.

This assignment due Thursday 9/11 by 8:00 PM. You'll need to submit a .zip file containing a Cargo project named hw1 in Gradescope. You can get the setup with:

cargo new hw1

You'll put all your solutions into the file hw1/src/main.rs. Please make sure to run cargo clean before submitting, i.e., don't sumbit the _build directory.

1. Collatz Conjecture

The Collatz conjecture asks if, for any positive integer \(n\), the following procedure eventually halts:

  • if \(n\) is \(1\), then HALT;
  • if \(n\) is even, divide \(n\) by \(2\);
  • if \(n\) is odd, multiple \(n\) by \(3\) and add \(1\).

Implementing this procedure has become a kind of FizzBuzz test when learning a new PL. To make it just a bit more interesting, implement the function verify_collatz which, given a number hi of type usize, verifies that the conjecture holds for every number up to and including hi. The function should not return anything, but you may want to include some print statements for your own benefit. Note that this function may not halt if the conjecture is false!

Determine a value of hi so that your implementation runs for a reasonable amount of time (let's say 5 minutes). How high can your implementation go?

Note: Make sure to run using cargo run --release to get full speed.

2. Pólya Conjecture

The Pólya conjecture, in rough terms, asks if, for any positive integer \(n\) greater than \(1\), at least half of all positive integers at most \(n\) have an odd number of prime factors (This has surprising connection to the Riemann hypothesis).

More formally, define \(\Omega : \mathbb N^+ \to \mathbb N\) so that \(\Omega(n)\) is the number of prime factors of \(n\) counting multiplicites. For example, \(\Omega(18) = 3\) since \(18 = 2 * 3 * 3\). Define \(\lambda : \mathbb N^+ \to \{-1, 1\}\) as

\begin{align*} \lambda(n) = (-1)^{\Omega(n)} \end{align*}

and \(L : \mathbb N^+ \to \mathbb Z\) as

\begin{align*} L(n) = \sum_{i = 1}^n \lambda(n) \end{align*}

Note that \(L(n) \leq 0\) if and only if at least half of all positive integers at most \(n\) have an odd number of prime factors.

The Pólya conjecture has been disproved, and the smallest counterexample is just shy of a billion: \(906,150,257\). When this number was found, it required some serious compute to verify. Our goal is to verify it on our laptops with a couple lines of code.

Implement the function verify_polya which, given a number hi of type usize, verifies the conjecture up to and including hi. The function should not return anything, but should stop if it finds a counterexample (and print it).

Find a value of hi so that your implementation runs for a reasonable amount of time (let's say 5 minutes). How high can your implementation go?

Note: Make sure to run using cargo run --release to get full speed.

3. Optimizing Pólya

(Optional) In the previous question you're not required to verify the minimal counterexample. The naïve solution likely won't be able to do this. The challenge: try to make your implementation in the previous problem as fast as possible. A couple things to try:

  • Make sure you're cutting out any unnecessary computations of \(\Omega\).
  • Compute prime numbers while computing \(\Omega\) and use those for later computations of the values of \(\Omega\) (you may want to look up Vec if you're interested in trying this).
  • If you just want to verify the smallest counterexample, be careful about your choice of number types.
  • Use a crate like slow_primes to help you with prime number calculations.
  • Use a crate like bit_vec to store \(\lambda\) in a bit vector.

My implementation runs in about 100s on my laptop. Once you've optimized, have fun with other side-quests:

  • Dealing with integer overflow (note that integer overflow is not checked at runtime in release mode).
  • Determine how many counter examples there are below a billion.
  • Determine the largest value of \(L\) below a billion.

4. Optimizing Collatz

(Double Optional) As in the previous problem, the naïve implementation of verify_collatz won't get you that far. But there are a lot of optimizations which can be done to make the code faster. In fact, a paper came out this year which gives improved verification algorithms for Collatz. The challenge: make your implementation of the first problem as fast as possible. If you want a target, take a look at this table and the definition of a path record. How high of a path record can you verify on your laptop? My not-terribly-optimized solution only gets me to about \(50\) in 5 minutes.