M12) Reproducible random inputs for a simulation

A supply chain simulation can use random draws to choose uncertain demand or supplier delays. A replication is one trial of the simulation. Each replication needs a sequence of random draws, one for each decision point. The draws are inputs to the simulation; they are not its changing state or its results.

The function receives positive integers n and m. It prepares one input sequence for each of n replications, with m decision points per replication. Each sequence uses its replication number as its seed and contains m successive draws from 0 up to but not including 1.

Table 1 shows two program runs with n = 2 and m = 3. Replication 1 uses seed 1, and replication 2 uses seed 2. The second program run produces the same draws.

random.Random(seed) creates a separate random-number generator with that starting seed. Each call to rng.random() draws the next decimal value from that generator, from 0 up to but not including 1. A new generator with the same seed produces the same sequence of values.

Table 1: Random draws from two program runs. Each run prepares two replications with three decision points. Replication 1 uses seed 1, and replication 2 uses seed 2. Values are rounded to three decimal places; paired rows match across runs.
Program
run
Replication
number
Draw at
point 1
Draw at
point 2
Draw at
point 3
1 1 0.134 0.847 0.764
1 2 0.956 0.948 0.057
2 1 0.134 0.847 0.764
2 2 0.956 0.948 0.057

Listing 1 shows four versions of make_random_inputs. Each returns a list of replications. Each replication is a two-item list: [rep_no, draws], where rep_no is the replication number and draws contains its m random values in decision-point order.

Which version prepares n reproducible replications with m decision points each, using the replication number as its seed?

Listing 1: Alternative implementations of a Python function that stores m successive random draws for each of n replications in nested lists (Question 1).
(a)
import random


def make_random_inputs(n, m):
    # Store each replication's draws together.
    inputs = []

    for rep_no in range(1, n + 1):
        rng = random.Random(n)
        draws = []
        for _ in range(m):
            draws.append(rng.random())
        inputs.append([rep_no, draws])

    return inputs
(b)
import random


def make_random_inputs(n, m):
    # Store each replication's draws together.
    inputs = []

    for rep_no in range(1, n + 1):
        draws = []
        for point in range(1, m + 1):
            rng = random.Random(point)
            draws.append(rng.random())
        inputs.append([rep_no, draws])

    return inputs
(c)
import random


def make_random_inputs(n, m):
    # Store each replication's draws together.
    inputs = []

    for rep_no in range(1, n + 1):
        draws = []
        for _ in range(m):
            rng = random.Random(rep_no)
            draws.append(rng.random())
        inputs.append([rep_no, draws])

    return inputs
(d)
import random


def make_random_inputs(n, m):
    # Store each replication's draws together.
    inputs = []

    for rep_no in range(1, n + 1):
        rng = random.Random(rep_no)
        draws = []
        for _ in range(m):
            draws.append(rng.random())
        inputs.append([rep_no, draws])

    return inputs