Skip to content

openstb.simulator.random

Support for random number generation.

Classes:

Name Description
ChunkedRNG

Chunked access to a set of random samples.

Functions:

Name Description
generate_seed

Generate a random seed.

ChunkedRNG

ChunkedRNG(seed, method, samples_per_item)

Chunked access to a set of random samples.

For some plugins, particularly targets, a large number of random samples may be required. To avoid loading them all into memory, they need to be accessed in chunks. In some cases, it may be required to access chunks repeatedly or in a non-sequential order.

This class wraps a standard numpy.random.Generator instance to provide on-demand access to chunks of its samples. When initialising the class, the desired distribution is selected. The number of samples assigned to each item is also fixed. For example, if used to randomise the position of targets then each item may need three samples, one for each coordinate.

The samples are not cached internally; repeated requests for a chunk will compute the samples each time. Internally, the sample space is sub-divided into blocks of a fixed size. A separate sub-generator (spawned from the main generator) is used for each block. The states of these sub-generators are stored, and they are reset or advanced as necessary to generate the samples for the requested chunk. This supports random access, but will be most efficient if the chunks are accessed sequentially. Resetting from the end and repeating the sequential access (i.e., get chunks 0 through N, then start back at 0 again) has minimal overhead for the reset. These are expected to be the most common patterns of use.

Parameters:

Name Type Description Default
seed int

The seed for the random number generator.

required
method str

The name of the sampling method to use. This determines the distribution the samples are drawn from. It can be the name of any method supported by the numpy.random.Generator class.

required
samples_per_item int

How many random samples are assigned to each item of the output.

required

Methods:

Name Description
sample

Sample the random number generator.

Source code in openstb/simulator/random.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def __init__(self, seed: int, method: str, samples_per_item: int):
    """
    Parameters
    ----------
    seed
        The seed for the random number generator.
    method
        The name of the sampling method to use. This determines the distribution the
        samples are drawn from. It can be the name of any method supported by the
        [numpy.random.Generator][] class.
    samples_per_item
        How many random samples are assigned to each item of the output.

    """
    self.seed = seed
    self.method = method
    self.samples_per_item = samples_per_item
    self._block_size = 5_000_000

    # Create a seed sequence and bit generator for the base RNG.
    ss = np.random.SeedSequence(seed)
    bg = np.random.PCG64DXSM(ss)
    self._base_rng = np.random.default_rng(bg)
    self._base_lock = SerializableLock()

    # Ensure the sampling method is valid.
    getattr(self._base_rng, self.method)

    # Initialise the list of block generators.
    self._block_rng: list[_BlockRNG] = []

sample

sample(start_index, count, *args, **kwargs)

Sample the random number generator.

Parameters:

Name Type Description Default
start_index int

The index of the first item to sample. Cannot be negative.

required
count int

The number of items to sample. Must be at least one.

required

Returns:

Name Type Description
samples ndarray

The samples drawn from the random number generator. This will have a shape (count, samples_per_item).

Source code in openstb/simulator/random.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
def sample(self, start_index: int, count: int, *args, **kwargs) -> np.ndarray:
    """Sample the random number generator.

    Parameters
    ----------
    start_index
        The index of the first item to sample. Cannot be negative.
    count
        The number of items to sample. Must be at least one.

    Returns
    -------
    samples : np.ndarray
        The samples drawn from the random number generator. This will have a shape
        (count, samples_per_item).

    """
    # Check we have valid inputs.
    if start_index < 0:
        raise IndexError(_("start index of RNG chunk cannot be negative"))
    if count < 1:
        raise IndexError(_("count of RNG chunk must be at least one"))

    # Determine which blocks we are operating in.
    end_index = start_index + count - 1
    start_block = start_index // self._block_size
    end_block = end_index // self._block_size
    stop_idx = end_block + 1

    # Ensure we have spawned RNGs for these blocks.
    with self._base_lock:
        missing = stop_idx - len(self._block_rng)
        if missing > 0:
            self._block_rng.extend(
                _BlockRNG(subrng, self.method, self.samples_per_item)
                for subrng in self._base_rng.spawn(missing)
            )

    # Get the samples from each of these.
    samples = []
    block_start = start_index % self._block_size
    remaining = count
    for block_idx in range(start_block, stop_idx):
        available = self._block_size - block_start
        block_count = remaining if remaining < available else available
        samples.append(
            self._block_rng[block_idx].sample(
                block_start, block_count, *args, **kwargs
            )
        )

        # All blocks apart from the first will sample from the start of the block.
        block_start = 0
        remaining -= block_count

    return np.concat(samples)

generate_seed

generate_seed()

Generate a random seed.

Internally, this uses numpy.random.SeedSequence to find a suitable source of entropy to form the seed.

Returns:

Name Type Description
seed int

A positive integer to use as a random seed. The size of the seed depends on the default SeedSequence settings; as of the time of writing, this integer will be at least 128 bits long.

Source code in openstb/simulator/random.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
def generate_seed() -> int:
    """Generate a random seed.

    Internally, this uses [numpy.random.SeedSequence][] to find a suitable source of
    entropy to form the seed.

    Returns
    -------
    seed : int
        A positive integer to use as a random seed. The size of the seed depends on the
        default SeedSequence settings; as of the time of writing, this integer will be
        at least 128 bits long.

    """
    seq = np.random.SeedSequence(None)
    if not isinstance(seq.entropy, int):
        raise RuntimeError(_("unexpected type of entropy when generating seed"))
    return seq.entropy