From eb468296aa6d173b7ead55933fefd0a87a6302d5 Mon Sep 17 00:00:00 2001 From: rzuckerm Date: Sun, 15 Dec 2024 12:51:24 -0600 Subject: [PATCH] Add Primes in Whitespace --- PrimeWhitespace/solution_1/Dockerfile | 19 ++ PrimeWhitespace/solution_1/README.md | 248 +++++++++++++++++++++ PrimeWhitespace/solution_1/build.sh | 2 + PrimeWhitespace/solution_1/primes-32bit.ws | 166 ++++++++++++++ PrimeWhitespace/solution_1/primes-64bit.ws | 166 ++++++++++++++ PrimeWhitespace/solution_1/run.sh | 2 + PrimeWhitespace/solution_1/run_primes.py | 105 +++++++++ PrimeWhitespace/solution_1/run_primes.sh | 3 + 8 files changed, 711 insertions(+) create mode 100644 PrimeWhitespace/solution_1/Dockerfile create mode 100644 PrimeWhitespace/solution_1/README.md create mode 100755 PrimeWhitespace/solution_1/build.sh create mode 100644 PrimeWhitespace/solution_1/primes-32bit.ws create mode 100644 PrimeWhitespace/solution_1/primes-64bit.ws create mode 100755 PrimeWhitespace/solution_1/run.sh create mode 100755 PrimeWhitespace/solution_1/run_primes.py create mode 100755 PrimeWhitespace/solution_1/run_primes.sh diff --git a/PrimeWhitespace/solution_1/Dockerfile b/PrimeWhitespace/solution_1/Dockerfile new file mode 100644 index 000000000..e739a8df8 --- /dev/null +++ b/PrimeWhitespace/solution_1/Dockerfile @@ -0,0 +1,19 @@ +FROM ubuntu:24.04 +RUN apt-get update && \ + apt-get install -y git make python3 ghc && \ + mkdir -p /opt && \ + cd /opt && \ + git clone --depth 1 https://github.com/TryItOnline/WSpace && \ + cd WSpace && \ + make && \ + cp wspace /usr/bin/whitespace && \ + cd / && \ + apt-get remove -y git make && \ + apt-get autoremove -y && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /opt/WSpace + +WORKDIR /opt/app +COPY *.ws *.sh *.py ./ + +ENTRYPOINT ["./run_primes.sh"] diff --git a/PrimeWhitespace/solution_1/README.md b/PrimeWhitespace/solution_1/README.md new file mode 100644 index 000000000..dfa29d4b9 --- /dev/null +++ b/PrimeWhitespace/solution_1/README.md @@ -0,0 +1,248 @@ +# Whitespace solution by rzuckerm + +![Algorithm](https://img.shields.io/badge/Algorithm-base-green) +![Faithfulness](https://img.shields.io/badge/Faithful-no-yellowgreen) +![Parallelism](https://img.shields.io/badge/Parallel-no-green) +![Bit count](https://img.shields.io/badge/Bits-1-green) +![Deviation](https://img.shields.io/badge/Deviation-sieve_size-blue) + +## Introduction + +Whitespace is an esoteric language was developed by Edwin Brady and Chris Morris. +The language is entirely made up of 3 whitespace character: space, tab, and newline. +Every other character is ignored. It is customary to make the code a little more +"readable" by marking each character with `S` for space, `T` for tab, and `N` for newline. +It has a very limited instruction set. See +[this Wikipedia article](https://en.wikipedia.org/wiki/Whitespace_%28programming_language%29) +for details. + +Since Whitespace is such a difficult language to work with, I wrote an +[assembler](https://github.com/rzuckerm/whitespace-asm) to generate the Whitespace code from +my own version of Whitespace Assembly language. The +[whitespace-primes](https://github.com/rzuckerm/whitespace-primes/tree/main) repository +has a [template](https://github.com/rzuckerm/whitespace-primes/blob/main/template/primes.ws.asm.j2) +that I used to generate the assembly code. The generated assembly code has a `.ws.asm` extension +and is in the [whitespace](https://github.com/rzuckerm/whitespace-primes/tree/main/whitespace) +directory. + +This is not intended as a serious solution. It was done more as a challenge to see if I could +actually implement something as complicated as a Prime Number Sieve in such a limited language and +have it complete in a reasonable amount of time. This solution is marked as "unfaithful" for the +following reasons: + +- I could only get the solution to complete in a reasonable amount of time for 100,000 or less. +- Since Whitespace has no ability to measure time, I had to write a python wrapper to run it. + +## Python implementation + +At a high level, the steps are as follows: + +- Parse the command-line arguments +- While time limit not expired, the Whitespace program for the appropriate number of bits per +- word is run, sending the sieve size to the `stdin` and capturing the `stdout`, keeping track + of the number of passes and the elapsed time +- The required information about the performance and accuracy of the Whitespace program is displayed. + The accuracy is determined by decoding the `stdout` of the Whitespace program and comparing the number + of primes found against the expected value for the sieve size + +## Whitespace implementation + +At a high level, the steps are as follows: + +- The sieve size (or limit) is taken from `stdin` and converted to an integer (`n`). +- The prime numbers up to `n` (for odd values starting at 3) are calculated, and the result + (`sieve`) is stored as bitmap in a set of memory addresses (the size of which is determined + by the number of bits per word), where `1` means composite, and `0` + means prime. The bits are numbered as follows: + * Bit 0: `3` + * Bit 1: `5` + * ... + * Bit `k`: `2*k + 3` + * ... + * Bit `(n - 3) // 2`: `n - (n mod 2)` (next lowest odd value -- e.g., 1000 becomes 999) +- The result (`sieve`) is output as a set space-separated decimal values that is decoded by the + python code. + +Before diving into the actual implementation of the prime sieve, let's take a look at the algorithm +first: + +``` +sieve = 0 +factor = 3 +while factor*factor <= n: + factor_bit = (factor - 3) // 2 + if bit "factor_bit" is not set in sieve: + inner_factor = factor*factor + while inner_factor <= n: + inner_factor_bit = (inner_factor - 3) // 2 + Set bit "inner_factor_bit" in sieve + inner_factor += 2*factor + + factor += 2 + +output sieve as space-separated decimal value +``` + +The actual implementation uses bit numbers instead of factors: + +``` +sieve = 0 +b = 0 +bsq = 3 +while bsq < B: + if bit b clear in sieve: + k = bsq + kinc = 2 * b + 3 + while k < B: + Set bit k in sieve + k += kinc + + b += 1 + bsq += 4 * (b + 1) + +output sieve as space-separated decimal value +``` + +where: + +- `b` is the bit number for the factor +- `bsq` is the bit number for the factor squared +- `B` is the total number of bits = `(n - 1) // 2` +- `k` is the bit number of the inner factor +- `kinc` is the increment for the inner factor + +Since the sieve bits start at a factor of 3 and only have odd values, the bit number can be +determined like this: + +``` +bit_number = (factor - 3) // 2 +``` + +Therefore, the initial value for `b` and `bsq` are determined as follows: + +``` +b = (3 - 3) // 2 = 0 +bsq = (3*3 - 3) // 2 = 6 // 2 = 3 +``` + +The value of `k` starts out as `bsq` (which corresponds to `factor**2`). The increment can be +determined by the difference between consecutive factors (`2*factor`): + +``` +factor = 2*b + 3 +f1 = m*factor = m*(2*b + 3) +k1 = (f1 - 3) // 2 = [m*(2*b + 3) - 3] // 2 +f2 = (m + 2)*factor = (m + 2)*(2*b + 3) +k2 = (f2 - 3) // 2 = [(m + 2)*(2*b + 3) - 3] // 2 +kinc = k2 - k1 = {[(m + 2)*(2*b + 3) - 3] - [m*(2*b + 3) - 3]} // 2 + = 2*(2*b + 3) // 2 = 2*b + 3 +``` + +The difference between consecutive `factor**2` values can be determined as follows: + +``` +factor_sq_diff = (factor + 2)**2 - factor**2 = 4*factor + 4 +bsq_diff = 4*(2*b + 3 - 3) // 2 + 4 = 4*b + 4 = 4*(b + 1) +``` + +Since Whitespace does not have any bitwise operations, this must be simulated as follows: + +- Testing if bit `x` is set in `y` is done by checking if `(y // 2**x) mod 2` is greater than zero, + where `2**x` is pre-computed +- Setting bit `x` of `y` is done by adding `2**x` to `y` if bit `x` is not set in `y`. + +Since the sieve is broken up into a number of W-bit words, the bitwise operations are +done on the following: + +``` +index = x // W +mask = 2**(x mod W) +``` + +where: + +- `index` is the index into the sieve words +- `mask` is the pre-computed values of `2**m` for `m` is `0` to `W-1` + +## Run instructions + +Build the docker image with this: + +```bash +./build.sh +``` + +You should only need to do this once. Run the docker image: + +```bash +./run.sh +``` + +## Command-line arguments + +You can add the following command-line arguments to `run.sh`: + +- `-b` or `--bits-per-word` - The number of bits per word. Default: 32 +- `-l ` or `--limit ` - Upper limit for calculating prime numbers. Default: 100000 +- `-t