Search code examples
pythonpython-3.xmontecarlorandom-seed

Python random number generation varying for the same seed


I am writing some Monte Carlo code (Python 3.7) and I am unable to find out why I get different results with the same random seed.

I have narrowed down to the function at which I start getting variations in the random results for the same seed. I create an instance of rng = random.Random() to make sure the other imports are not interfering with the random.seed. I have also tested a sequence of random numbers with the imports I use (only third-party is numpy) and this does not seem to be the issue either. My code is not multi-threaded either.

I setup my rng by:

rng = random.Random()
rng.seed(123)

The variations are starting here at this function (also there seems to be a pattern to the variation it can be consistent for some runs and then vary for some runs - back and forth):

def create_self_avoiding_walk(radii, origin, rng, max_iterations=10000):
    assert len(radii) > 0
    previous_radii = radii[0]
    previous_coords = origin
    new_coord_map = np.zeros((len(radii), 3))
    new_coord_map[0] = origin
    for i, radius in enumerate(radii):
        if i == 0:
            continue
        r = radius + previous_radii
        for iteration in range(0, max_iterations):
            theta = rng.uniform(0, 2 * np.pi)
            z = rng.uniform(-r, r)
            x = np.sqrt((r ** 2 - z ** 2)) * np.cos(theta)
            y = np.sqrt((r ** 2 - z ** 2)) * np.sin(theta)

            x += previous_coords[0]
            y += previous_coords[1]
            z += previous_coords[2]

            proposed_coords = [x, y, z]

            if coordinate_clash(np.array(proposed_coords), np.array(new_coord_map[:i]), radius, radii[:i]) is False:
                new_coord_map[i] = [x, y, z]
                previous_coords = [x, y, z]
                break
            if iteration == max_iterations - 1:  # Was unable to find non-clashing structure
                return np.array([])
    return new_coord_map

coordinate_clash calls the following functions:

@overload(np.float64, np.float64, np.float64, np.float64, np.float64, np.float64, float, float)
def coordinate_clash(x1, y1, z1, x2, y2, z2, radius1, radius2):
    return ((x1 - x2) ** 2) + ((y1 - y2) ** 2) + ((z1 - z2) ** 2) < (
                (radius1 + radius2) ** 2) - 1e-15  # Float PRECISION


@overload(np.ndarray, np.ndarray, float, float)
def coordinate_clash(vec1, vec2, radius1, radius2):
    return coordinate_clash(vec1[0], vec1[1], vec1[2], vec2[0], vec2[1], vec2[2], radius1, radius2)


@overload(np.ndarray, np.ndarray, float, list)
def coordinate_clash(vec1, mat, radius1, radii):
    for row, radius_entry in zip(mat, radii):
        if coordinate_clash(vec1, row, radius1, radius_entry):
            return True
    return False

Can anyone identify anything in the above code that would cause the random sequence to become inconsistent for the same seed?


Solution

  • I have now solved this issue. I was looking in the wrong place. My random instance was working as it should be. However, where I read and parsed the dataset was being parsed in different orders, which resulted in the fluctuations seen in the random number generation. The culprit was running looping over set(data) to set up my data storage. It appears that set() does not enforce the order so it would change on different runs. Therefore, this can straightforwardly be solved using sorted(set(data)).