-1

I have to implement the Wichmann-Hill random number generation algorithm in python (3.x) for an assignment. The algorithm needs to be seeded with three random numbers.

I tried seeding it with time.time_ns(), but if I try generating a new number many times in a row (I have to be able to do at least 100 000 consecutively) I get a bunch of repetitions because evidently the time hasn't changed yet.

The algorithm for Wichmann-Hill is as follows:

    i1 = (170 * i1) % 30323
    i2 = (171 * i2) % 30269
    i3 = (172 * i3) % 30307

    i1 = i1 / 30323.0
    i2 = i2 / 30269.0
    i3 = i3 / 30307.0
    return (i1 + i2 + i3) % 1.0

Where i1, i2 and i3 are supposed to be the seeds.

I'm stuck on finding a way to seed the algorithm for the next time a number is needed. Any advice is much appreciated.

  • How exactly are you initializing `i1`, `i2`, and `i3`, three consecutive calls to `time.time_ns`? – chepner Aug 08 '19 at 20:36
  • I don't think you are supposed to set `i1 /= 30323`, etc, though, based on https://en.wikipedia.org/wiki/Wichmann–Hill. – chepner Aug 08 '19 at 20:38

2 Answers2

-1

If you can manage with seeds in range 0 to 2**31-1, you can use numpys random.randint(low,high,size):

#!/usr/bin/env python3
import numpy as np
import time

SIZE = 100000
def WichmannHill(s1, s2, s3):
    i1 = (170 * s1) % 30323
    i2 = (171 * s2) % 30269
    i3 = (172 * s3) % 30307

    i1 = i1 / 30323.0
    i2 = i2 / 30269.0
    i3 = i3 / 30307.0
    return (i1 + i2 + i3) % 1.0

t0 = time.time()
np.random.seed(12345678)
seeds = []
for n in range(3):
    seeds.append(np.random.randint(low=1, high=2**31-1, size=SIZE))
print(f"gen seeds of length:{len(seeds[0])} in time:{time.time()-t0:.4f}sec")

t1 = time.time()
wh=[]
for n in range(len(seeds[0])):
    wh.append(WichmannHill(seeds[0][n], seeds[1][n], seeds[2][n]))
print(f"wh of length:{len(wh)} in time:{time.time()-t1:.4f}sec")

I've not checked for collisions:

gen seeds of length:100000 in time:0.0024sec
wh of length:100000 in time:1.1818sec
MortenB
  • 2,749
  • 1
  • 31
  • 35
-1

Based on this description of the algorithm, your implementation should look something like

class WHGen:
    def __init__(self):
        # Initialize the seeds with "random" numbers between 1 and 30,000.
        # Note: if you don't actually have nanosecond precision, you'll want
        # to divide time.time_ns() by some power of 10 first to discard
        # any low-order zeros.
        self.i1 = time.time_ns() % 30000 + 1
        self.i2 = time.time_ns() % 30000 + 1
        self.i3 = time.time_ns() % 30000 + 1
        # If you want to "cheat", you can just random.randint(1,30000) instead.

    def __iter__(self):
        while True:
            # The new seed is just the remainder, not the result of
            # the division used to compute the next number in the sequence.
            self.i1 = (171 * self.i1) % 30369
            self.i2 = (172 * self.i2) % 30307
            self.i3 = (170 * self.i3) % 30323

            # Do *not* reset the seeds themselves to these results.
            # I.e., not self.i1 /= 30269
            x = self.i1 / 30269
            y = self.i2 / 30307
            z = self.i3 / 30323

            yield (x + y + z) % 1.0

# 10 random numbers
gen = WHGen()
nums = list(islice(gen, 10))
chepner
  • 497,756
  • 71
  • 530
  • 681
  • What does `__iter__(self)` do? I assume it's making the function iterable somehow but I've never seen that before. Also, I won't be able to use randint because I'm not allowed to use any built-in RNG (will add to the question description). In my case with what you wrote here, all three my seeds would be the same, but I guess it should work since they get modded and divided and so on. – Armandt Fourie Aug 10 '19 at 06:30
  • `__iter__` defines what `iter(WHGen())` means, anything that tries to use `WHGen()` as an iterable will get an infinite sequence of values (lazily, of course). – chepner Aug 10 '19 at 12:54