|
9 | 9 | "\n",
|
10 | 10 | "If you are working in simulation modelling in Python, you will likely need to use `numpy.random` namespace. It provides a variety of statistical distributions which you can use for efficient sampling. \n",
|
11 | 11 | "\n",
|
12 |
| - "This notebook will guide you through an example of generating **100,000 samples** from the **uniform**, **exponential** and **normal** distributions." |
| 12 | + "This notebook will guide you through examples of \n", |
| 13 | + "\n", |
| 14 | + "1. Creating instances of a high quality Pseudo Random Number Generator (PRNG) using PCG64 provided by `numpy`\n", |
| 15 | + "2. Generating samples from the **uniform**, **exponential** and **normal** distributions.\n", |
| 16 | + "3. Spawning multiple non-overlapping streams of random numbers\n", |
| 17 | + "4. Using OOP to encapsulate PRNGs, distributions and parameters for simulation models." |
13 | 18 | ]
|
14 | 19 | },
|
15 | 20 | {
|
|
71 | 76 | " '''\n",
|
72 | 77 | " hist = np.histogram(samples, bins=np.arange(bins), \n",
|
73 | 78 | " density=True)\n",
|
74 |
| - " \n", |
| 79 | + "\n", |
75 | 80 | " fig = plt.figure(figsize=figsize)\n",
|
76 | 81 | " ax = fig.add_subplot()\n",
|
77 | 82 | " _ = ax.plot(hist[0])\n",
|
|
88 | 93 | "source": [
|
89 | 94 | "## 3. Creating a random number generator object\n",
|
90 | 95 | "\n",
|
91 |
| - "To generate random numbers for sampling from each distribution, we can use the `default_rng()` function from the `numpy.random` module.\n", |
| 96 | + "To generate pseudo random numbers for sampling from each distribution, we can use the `default_rng()` function from the `numpy.random` module.\n", |
92 | 97 | "\n",
|
93 |
| - "This function constructs an instance of a `Generator` class, which can produce random numbers. For more information on `Generator` you can look at [`numpy` online documentation.](https://numpy.org/doc/stable/reference/random/generator.html)" |
| 98 | + "This function constructs an instance of a `Generator` class, which can produce random numbers. \n", |
| 99 | + "\n", |
| 100 | + "By default `numpy` uses a Pseudo-Random Number Generator (PRNG) called use of the [Permuted Congruential Generator 64-bit](https://www.pcg-random.org/) (PCG64; period = $2^{128}$; maximum number of streams = $2^{63}$).\n", |
| 101 | + "\n", |
| 102 | + "For more information on `Generator` you can look at [`numpy` online documentation.](https://numpy.org/doc/stable/reference/random/generator.html)" |
94 | 103 | ]
|
95 | 104 | },
|
96 | 105 | {
|
|
147 | 156 | "samples = rng.uniform(low=10, high=40, size=1_000_000)\n",
|
148 | 157 | "\n",
|
149 | 158 | "# Illustrate with plot.\n",
|
150 |
| - "fig, ax = distribution_plot(samples, bins=50)" |
| 159 | + "_ = distribution_plot(samples, bins=50)" |
151 | 160 | ]
|
152 | 161 | },
|
153 | 162 | {
|
|
165 | 174 | "metadata": {},
|
166 | 175 | "outputs": [],
|
167 | 176 | "source": [
|
168 |
| - "rng = np.random.default_rng()\n", |
| 177 | + "rng = np.random.default_rng(42)\n", |
169 | 178 | "samples = rng.exponential(scale=12, size=1_000_000)\n",
|
170 |
| - "distribution_plot(samples, bins=50)" |
| 179 | + "_ = distribution_plot(samples, bins=50)" |
171 | 180 | ]
|
172 | 181 | },
|
173 | 182 | {
|
|
185 | 194 | "metadata": {},
|
186 | 195 | "outputs": [],
|
187 | 196 | "source": [
|
188 |
| - "rng = np.random.default_rng()\n", |
| 197 | + "rng = np.random.default_rng(42)\n", |
189 | 198 | "samples = rng.normal(loc=25.0, scale=5.0, size=1_000_000)\n",
|
190 |
| - "distribution_plot(samples, bins=50)" |
| 199 | + "_ = distribution_plot(samples, bins=50)" |
191 | 200 | ]
|
192 | 201 | },
|
193 | 202 | {
|
|
218 | 227 | "id": "6ed34200-c5ac-4a70-ae83-90adf48aee67",
|
219 | 228 | "metadata": {},
|
220 | 229 | "source": [
|
221 |
| - "Note that you can also set `size` to 1. Just be aware that an array is returned. e.g." |
| 230 | + "**Note** that you can also set `size` to 1. Just be aware that an array is returned. e.g." |
222 | 231 | ]
|
223 | 232 | },
|
224 | 233 | {
|
|
238 | 247 | "print(sample[0])"
|
239 | 248 | ]
|
240 | 249 | },
|
| 250 | + { |
| 251 | + "cell_type": "markdown", |
| 252 | + "id": "b4bbf593-fa4a-4347-acb8-2f3df370d6ef", |
| 253 | + "metadata": {}, |
| 254 | + "source": [ |
| 255 | + "## 5. Spawning multiple non-overlapping PRN streams.\n", |
| 256 | + "\n", |
| 257 | + "For simulation we ideally want to use multiple streams of random numbers that do not overlap (i.e. they are independent). This is straightforward to implement in Python using `SeedSequence` and a user provided integer seed and the number of independent streams to spawn.\n", |
| 258 | + "\n", |
| 259 | + "> As a user we don't need to worry about the quality of the integer seed provided. This is useful for implementing multiple replications and common random numbers." |
| 260 | + ] |
| 261 | + }, |
| 262 | + { |
| 263 | + "cell_type": "markdown", |
| 264 | + "id": "82a41ef0-7198-403d-9fc9-a73eb293655a", |
| 265 | + "metadata": {}, |
| 266 | + "source": [ |
| 267 | + "Here's how we create the seeds from a single user supplied seed. The returned variable `seeds` is a Python `List`." |
| 268 | + ] |
| 269 | + }, |
| 270 | + { |
| 271 | + "cell_type": "code", |
| 272 | + "execution_count": null, |
| 273 | + "id": "4c404fa0-0987-4bff-9a1d-e784c485e59f", |
| 274 | + "metadata": {}, |
| 275 | + "outputs": [], |
| 276 | + "source": [ |
| 277 | + "n_streams = 2\n", |
| 278 | + "user_seed = 1\n", |
| 279 | + "\n", |
| 280 | + "seed_sequence = np.random.SeedSequence(user_seed)\n", |
| 281 | + "seeds = seed_sequence.spawn(n_streams)" |
| 282 | + ] |
| 283 | + }, |
| 284 | + { |
| 285 | + "cell_type": "markdown", |
| 286 | + "id": "002292d7-7bd8-4fec-9eb0-b9a2f6459b4f", |
| 287 | + "metadata": {}, |
| 288 | + "source": [ |
| 289 | + "We use `seeds` when creating our PRNGs. For example, one for inter-arrival times and one for service times." |
| 290 | + ] |
| 291 | + }, |
| 292 | + { |
| 293 | + "cell_type": "code", |
| 294 | + "execution_count": null, |
| 295 | + "id": "268d5a1b-0372-4d4e-b38d-75b3b9b8990e", |
| 296 | + "metadata": {}, |
| 297 | + "outputs": [], |
| 298 | + "source": [ |
| 299 | + "# e.g. to model arrival times\n", |
| 300 | + "arrival_rng = np.random.default_rng(seeds[0])\n", |
| 301 | + "\n", |
| 302 | + "# e.g. to model service times\n", |
| 303 | + "service_rng = np.random.default_rng(seeds[1])" |
| 304 | + ] |
| 305 | + }, |
241 | 306 | {
|
242 | 307 | "cell_type": "markdown",
|
243 | 308 | "id": "0dc23b86-1ae1-4da0-b393-97dcf884f442",
|
|
273 | 338 | " mean: float\n",
|
274 | 339 | " The mean of the exponential distribution\n",
|
275 | 340 | "\n",
|
276 |
| - " random_seed: int, optional (default=None)\n", |
| 341 | + " random_seed: int | SeedSequence, optional (default=None)\n", |
277 | 342 | " A random seed to reproduce samples. If set to none then a unique\n",
|
278 | 343 | " sample is created.\n",
|
279 | 344 | " '''\n",
|
|
0 commit comments