diff --git a/content/10_multiple_arrival_processes.ipynb b/content/10_multiple_arrival_processes.ipynb new file mode 100644 index 0000000..a2712a0 --- /dev/null +++ b/content/10_multiple_arrival_processes.ipynb @@ -0,0 +1,819 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0be7dabf-cb34-4faf-abb1-e2c8e735beda", + "metadata": {}, + "source": [ + "# Multiple arrival processes\n", + "\n", + "In this notebook we will learn how to include multiple arrivals processes in a `simpy` model. We will use exponential distributions here, but any type of distribution, time dependency, of schedule could be included instead. \n", + "\n", + "We will work with a hypothetical hospital that provides emergency orthopedic surgery to different classes of patient.\n", + "\n", + "![model image](img/multiple_arrivals.png \"Urgent care call centre\")\n", + "\n", + "| ID | Arrival Type | Distribution | Mean (mins) |\n", + "|----|-----------------|--------------|-------------|\n", + "| 1 | Shoulder Trauma | Exponential | 24.0 |\n", + "| 2 | Hip Fracture | Exponential | 32.0 |\n", + "| 3 | Wrist Fracture | Exponential | 21.0 |\n", + "| 4 | Ankle Fracture | Exponential | 17.0 |\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "0d9383eb-420c-49f8-b178-f2fe9e6b2a90", + "metadata": {}, + "source": [ + "## 1. Imports " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "c1cee9f9-8696-4b13-94ff-bee2a2a2e5f8", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import itertools\n", + "import simpy" + ] + }, + { + "cell_type": "markdown", + "id": "c422046d-488a-4743-8ad4-97e9f3dab420", + "metadata": {}, + "source": [ + "## 2. Constants" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1ecf0429-f03f-4ad2-abb4-46692a74e559", + "metadata": {}, + "outputs": [], + "source": [ + "# default mean inter-arrival times(exp)\n", + "IAT_SHOULDER = 24.0\n", + "IAT_HIP = 32.0\n", + "IAT_WRIST = 21.0\n", + "IAT_ANKLE = 17.0\n", + "\n", + "# sampling settings\n", + "N_STREAMS = 4\n", + "DEFAULT_RND_SET = 0\n", + "\n", + "# Boolean switch to simulation results as the model runs\n", + "TRACE = False\n", + "\n", + "# run variables (units = hours)\n", + "RUN_LENGTH = 24*10" + ] + }, + { + "cell_type": "markdown", + "id": "5f2a4ad9-6d5e-480d-850f-84d4882a738b", + "metadata": {}, + "source": [ + "## 2. Helper classes and functions" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7cbe448d-696f-48fe-834c-7bc833112245", + "metadata": {}, + "outputs": [], + "source": [ + "class Exponential:\n", + " \"\"\"\n", + " Convenience class for the exponential distribution.\n", + " packages up distribution parameters, seed and random generator.\n", + " \"\"\"\n", + "\n", + " def __init__(self, mean, random_seed=None):\n", + " \"\"\"\n", + " Constructor\n", + "\n", + " Params:\n", + " ------\n", + " mean: float\n", + " The mean of the exponential distribution\n", + "\n", + " random_seed: int| SeedSequence, optional (default=None)\n", + " A random seed to reproduce samples. If set to none then a unique\n", + " sample is created.\n", + " \"\"\"\n", + " self.rand = np.random.default_rng(seed=random_seed)\n", + " self.mean = mean\n", + "\n", + " def sample(self, size=None):\n", + " \"\"\"\n", + " Generate a sample from the exponential distribution\n", + "\n", + " Params:\n", + " -------\n", + " size: int, optional (default=None)\n", + " the number of samples to return. If size=None then a single\n", + " sample is returned.\n", + "\n", + " Returns:\n", + " -------\n", + " float or np.ndarray (if size >=1)\n", + " \"\"\"\n", + " return self.rand.exponential(self.mean, size=size)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "52c9271f-1d05-454d-a199-8768bdf5b6e8", + "metadata": {}, + "outputs": [], + "source": [ + "def trace(msg):\n", + " \"\"\"\n", + " Turing printing of events on and off.\n", + "\n", + " Params:\n", + " -------\n", + " msg: str\n", + " string to print to screen.\n", + " \"\"\"\n", + " if TRACE:\n", + " print(msg)" + ] + }, + { + "cell_type": "markdown", + "id": "5a8c050c-4bb6-408f-a805-3a4aaab56916", + "metadata": {}, + "source": [ + "## 3. Experiment class" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "576ae9b4-b21b-4ed0-9b13-e5898d423173", + "metadata": {}, + "outputs": [], + "source": [ + "class Experiment:\n", + " \"\"\"\n", + " Encapsulates the concept of an experiment 🧪 for the Orthopedic Surgey\n", + " trauma arrival simulator. Manages parameters, PRNG streams and results.\n", + " \"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " random_number_set=DEFAULT_RND_SET,\n", + " n_streams=N_STREAMS,\n", + " iat_shoulder=IAT_SHOULDER,\n", + " iat_hip=IAT_HIP,\n", + " iat_wrist=IAT_WRIST,\n", + " iat_ankle=IAT_ANKLE,\n", + " ):\n", + " \"\"\"\n", + " The init method sets up our defaults.\n", + " \"\"\"\n", + " # sampling\n", + " self.random_number_set = random_number_set\n", + " self.n_streams = n_streams\n", + "\n", + " # store parameters for the run of the model\n", + " self.iat_shoulder = iat_shoulder\n", + " self.iat_hip = iat_hip\n", + " self.iat_wrist = iat_wrist\n", + " self.iat_ankle = iat_ankle\n", + "\n", + " # initialise results to zero\n", + " self.init_results_variables()\n", + "\n", + " # initialise sampling objects\n", + " self.init_sampling()\n", + "\n", + " def set_random_no_set(self, random_number_set):\n", + " \"\"\"\n", + " Controls the random sampling\n", + " Parameters:\n", + " ----------\n", + " random_number_set: int\n", + " Used to control the set of pseudo random numbers used by\n", + " the distributions in the simulation.\n", + " \"\"\"\n", + " self.random_number_set = random_number_set\n", + " self.init_sampling()\n", + "\n", + " def init_sampling(self):\n", + " \"\"\"\n", + " Create the distributions used by the model and initialise\n", + " the random seeds of each.\n", + " \"\"\"\n", + " # produce n non-overlapping streams\n", + " seed_sequence = np.random.SeedSequence(self.random_number_set)\n", + " self.seeds = seed_sequence.spawn(self.n_streams)\n", + "\n", + " # create distributions\n", + "\n", + " # inter-arrival time distributions\n", + " self.arrival_shoulder = Exponential(\n", + " self.iat_shoulder, random_seed=self.seeds[0]\n", + " )\n", + "\n", + " self.arrival_hip = Exponential(\n", + " self.iat_hip, random_seed=self.seeds[0]\n", + " )\n", + "\n", + " self.arrival_wrist = Exponential(\n", + " self.iat_wrist, random_seed=self.seeds[0]\n", + " )\n", + "\n", + " self.arrival_ankle = Exponential(\n", + " self.iat_ankle, random_seed=self.seeds[0]\n", + " )\n", + " \n", + "\n", + " def init_results_variables(self):\n", + " \"\"\"\n", + " Initialise all of the experiment variables used in results\n", + " collection. This method is called at the start of each run\n", + " of the model\n", + " \"\"\"\n", + " # variable used to store results of experiment\n", + " self.results = {}\n", + " self.results[\"n_shoulders\"] = 0\n", + " self.results[\"n_hips\"] = 0\n", + " self.results[\"n_wrists\"] = 0\n", + " self.results[\"n_ankles\"] = 0" + ] + }, + { + "cell_type": "markdown", + "id": "de8990c2-a330-4c02-ac77-26c30d3e0a41", + "metadata": {}, + "source": [ + "## 4. A function per arrival source\n", + "\n", + "The first approach we will use is creating an arrival generator per source. There will be some code redundancy, but it will a clear design for others to understand." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b3e686ce-5371-4471-a052-b9d43309bc85", + "metadata": {}, + "outputs": [], + "source": [ + "def shoulder_arrivals_generator(env, args):\n", + " \"\"\"\n", + " Arrival process for shoulders.\n", + "\n", + " Parameters:\n", + " ------\n", + " env: simpy.Environment\n", + " The simpy environment for the simulation\n", + "\n", + " args: Experiment\n", + " The settings and input parameters for the simulation.\n", + " \"\"\"\n", + " # use itertools as it provides an infinite loop\n", + " # with a counter variable that we can use for unique Ids\n", + " for patient_count in itertools.count(start=1):\n", + "\n", + " # the sample distribution is defined by the experiment.\n", + " inter_arrival_time = args.arrival_shoulder.sample()\n", + " yield env.timeout(inter_arrival_time)\n", + "\n", + " args.results[\"n_shoulders\"] = patient_count\n", + " \n", + " trace(f\"{env.now:.2f}: SHOULDER arrival.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "2c55c7fa-354a-4159-9f3b-e04d08a91ee1", + "metadata": {}, + "outputs": [], + "source": [ + "def hip_arrivals_generator(env, args):\n", + " \"\"\"\n", + " Arrival process for hips.\n", + "\n", + " Parameters:\n", + " ------\n", + " env: simpy.Environment\n", + " The simpy environment for the simulation\n", + "\n", + " args: Experiment\n", + " The settings and input parameters for the simulation.\n", + " \"\"\"\n", + " # use itertools as it provides an infinite loop\n", + " # with a counter variable that we can use for unique Ids\n", + " for patient_count in itertools.count(start=1):\n", + "\n", + " # the sample distribution is defined by the experiment.\n", + " inter_arrival_time = args.arrival_hip.sample()\n", + " yield env.timeout(inter_arrival_time)\n", + "\n", + " args.results[\"n_hips\"] = patient_count\n", + " trace(f\"{env.now:.2f}: HIP arrival.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "4b231779-eea8-4aea-b6fd-03625b8aedbc", + "metadata": {}, + "outputs": [], + "source": [ + "def wrist_arrivals_generator(env, args):\n", + " \"\"\"\n", + " Arrival process for wrists.\n", + "\n", + " Parameters:\n", + " ------\n", + " env: simpy.Environment\n", + " The simpy environment for the simulation\n", + "\n", + " args: Experiment\n", + " The settings and input parameters for the simulation.\n", + " \"\"\"\n", + " # use itertools as it provides an infinite loop\n", + " # with a counter variable that we can use for unique Ids\n", + " for patient_count in itertools.count(start=1):\n", + "\n", + " # the sample distribution is defined by the experiment.\n", + " inter_arrival_time = args.arrival_wrist.sample()\n", + " yield env.timeout(inter_arrival_time)\n", + "\n", + " args.results[\"n_wrists\"] = patient_count\n", + " trace(f\"{env.now:.2f}: WRIST arrival.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "6a06c195-5d2c-4ac0-b393-80b1578ff81b", + "metadata": {}, + "outputs": [], + "source": [ + "def ankle_arrivals_generator(env, args):\n", + " \"\"\"\n", + " Arrival process for ankles.\n", + "\n", + " Parameters:\n", + " ------\n", + " env: simpy.Environment\n", + " The simpy environment for the simulation\n", + "\n", + " args: Experiment\n", + " The settings and input parameters for the simulation.\n", + " \"\"\"\n", + " # use itertools as it provides an infinite loop\n", + " # with a counter variable that we can use for unique Ids\n", + " for patient_count in itertools.count(start=1):\n", + "\n", + " # the sample distribution is defined by the experiment.\n", + " inter_arrival_time = args.arrival_wrist.sample()\n", + " yield env.timeout(inter_arrival_time)\n", + "\n", + " args.results[\"n_ankles\"] = patient_count\n", + " trace(f\"{env.now:.2f}: ANKLE arrival.\")" + ] + }, + { + "cell_type": "markdown", + "id": "6058571e-9fdb-4961-be27-8a3b8c2fe26e", + "metadata": {}, + "source": [ + "## 5. Single run function" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0d0ea6cf-7d95-4d2c-9690-fcdbdae35d84", + "metadata": {}, + "outputs": [], + "source": [ + "def single_run(\n", + " experiment, \n", + " rep=0,\n", + " run_length=RUN_LENGTH\n", + "):\n", + " \"\"\"\n", + " Perform a single run of the model and return the results\n", + "\n", + " Parameters:\n", + " -----------\n", + "\n", + " experiment: Experiment\n", + " The experiment/paramaters to use with model\n", + "\n", + " rep: int\n", + " The replication number.\n", + "\n", + " rc_period: float, optional (default=RUN_LENGTH)\n", + " The run length of the model\n", + " \"\"\"\n", + "\n", + " # reset all results variables to zero and empty\n", + " experiment.init_results_variables()\n", + "\n", + " # set random number set to the replication no.\n", + " # this controls sampling for the run.\n", + " experiment.set_random_no_set(rep)\n", + "\n", + " # environment is (re)created inside single run\n", + " env = simpy.Environment()\n", + "\n", + " # we pass all arrival generators to simpy \n", + " env.process(shoulder_arrivals_generator(env, experiment))\n", + " env.process(hip_arrivals_generator(env, experiment))\n", + " env.process(wrist_arrivals_generator(env, experiment))\n", + " env.process(ankle_arrivals_generator(env, experiment))\n", + "\n", + " # run for warm-up + results collection period\n", + " env.run(until=run_length)\n", + "\n", + " # return the count of the arrivals\n", + " return experiment.results" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "caf52390-5455-4fa1-bb22-60b5b91ad8d0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "16.03: ANKLE arrival.\n", + "42.33: ANKLE arrival.\n", + "47.07: ANKLE arrival.\n", + "51.71: ANKLE arrival.\n", + "69.16: WRIST arrival.\n", + "70.55: WRIST arrival.\n", + "79.04: SHOULDER arrival.\n", + "89.46: WRIST arrival.\n", + "89.50: ANKLE arrival.\n", + "95.66: WRIST arrival.\n", + "97.36: SHOULDER arrival.\n", + "105.39: HIP arrival.\n", + "127.42: SHOULDER arrival.\n", + "129.81: HIP arrival.\n", + "132.84: SHOULDER arrival.\n", + "134.69: WRIST arrival.\n", + "135.00: WRIST arrival.\n", + "137.80: ANKLE arrival.\n", + "138.14: SHOULDER arrival.\n", + "154.52: ANKLE arrival.\n", + "161.86: ANKLE arrival.\n", + "169.89: HIP arrival.\n", + "177.12: HIP arrival.\n", + "181.34: SHOULDER arrival.\n", + "182.92: SHOULDER arrival.\n", + "184.18: HIP arrival.\n", + "203.25: ANKLE arrival.\n", + "204.53: SHOULDER arrival.\n", + "211.62: SHOULDER arrival.\n", + "218.83: ANKLE arrival.\n", + "218.96: ANKLE arrival.\n", + "236.34: ANKLE arrival.\n" + ] + }, + { + "data": { + "text/plain": [ + "{'n_shoulders': 9, 'n_hips': 5, 'n_wrists': 6, 'n_ankles': 12}" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "TRACE = True\n", + "experiment = Experiment()\n", + "results = single_run(experiment)\n", + "results" + ] + }, + { + "cell_type": "markdown", + "id": "a48ffebd-5af0-4354-89bc-7de77ee60e8b", + "metadata": {}, + "source": [ + "## A hospital that only provides surgery for hip fractures" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "8228ab0c-cc99-48e2-a2c9-c9dcce8d854f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "105.39: HIP arrival.\n", + "129.81: HIP arrival.\n", + "169.89: HIP arrival.\n", + "177.12: HIP arrival.\n", + "184.18: HIP arrival.\n" + ] + }, + { + "data": { + "text/plain": [ + "{'n_shoulders': 0, 'n_hips': 5, 'n_wrists': 0, 'n_ankles': 0}" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "M = 1_000_000\n", + "experiment = Experiment(iat_shoulder=M, iat_wrist=M, iat_ankle=M)\n", + "results = single_run(experiment)\n", + "results" + ] + }, + { + "cell_type": "markdown", + "id": "fd22a6fd-563c-40d4-a981-e8a72cd136ad", + "metadata": {}, + "source": [ + "## A single arrival generator function\n", + "\n", + "We can write less code by modifying the `Experiment` class to use `dict` to store the distributions and by creating a generator function that accepts parameters. \n", + "\n", + "The code is more flexible, at the cost of readability (and potential to make more mistakes) for some less experienced coders." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "c16da1f8-8785-49be-9ee2-06d6e0a803d6", + "metadata": {}, + "outputs": [], + "source": [ + "class Experiment:\n", + " \"\"\"\n", + " Encapsulates the concept of an experiment 🧪 for the Orthopedic Surgey\n", + " trauma arrival simulator. Manages parameters, PRNG streams and results.\n", + " \"\"\"\n", + "\n", + " def __init__(\n", + " self,\n", + " random_number_set=DEFAULT_RND_SET,\n", + " n_streams=N_STREAMS,\n", + " iat_shoulder=IAT_SHOULDER,\n", + " iat_hip=IAT_HIP,\n", + " iat_wrist=IAT_WRIST,\n", + " iat_ankle=IAT_ANKLE,\n", + " ):\n", + " \"\"\"\n", + " The init method sets up our defaults.\n", + " \"\"\"\n", + " # sampling\n", + " self.random_number_set = random_number_set\n", + " self.n_streams = n_streams\n", + "\n", + " # store parameters for the run of the model\n", + " self.iat_shoulder = iat_shoulder\n", + " self.iat_hip = iat_hip\n", + " self.iat_wrist = iat_wrist\n", + " self.iat_ankle = iat_ankle\n", + " \n", + " # we will store all code in distributions\n", + " self.dists = {}\n", + "\n", + " # initialise results to zero\n", + " self.init_results_variables()\n", + "\n", + " # initialise sampling objects\n", + " self.init_sampling()\n", + "\n", + " def set_random_no_set(self, random_number_set):\n", + " \"\"\"\n", + " Controls the random sampling\n", + " Parameters:\n", + " ----------\n", + " random_number_set: int\n", + " Used to control the set of pseudo random numbers used by\n", + " the distributions in the simulation.\n", + " \"\"\"\n", + " self.random_number_set = random_number_set\n", + " self.init_sampling()\n", + "\n", + " def init_sampling(self):\n", + " \"\"\"\n", + " Create the distributions used by the model and initialise\n", + " the random seeds of each.\n", + " \"\"\"\n", + " # produce n non-overlapping streams\n", + " seed_sequence = np.random.SeedSequence(self.random_number_set)\n", + " self.seeds = seed_sequence.spawn(self.n_streams)\n", + "\n", + " # create distributions\n", + " \n", + " # inter-arrival time distributions\n", + " self.dists[\"shoulder\"] = Exponential(\n", + " self.iat_shoulder, random_seed=self.seeds[0]\n", + " )\n", + " \n", + " self.dists[\"hip\"] = Exponential(\n", + " self.iat_hip, random_seed=self.seeds[1]\n", + " )\n", + "\n", + " self.dists[\"wrist\"] = Exponential(\n", + " self.iat_wrist, random_seed=self.seeds[2]\n", + " )\n", + "\n", + " self.dists[\"ankle\"] = Exponential(\n", + " self.iat_ankle, random_seed=self.seeds[3]\n", + " )\n", + " \n", + "\n", + " def init_results_variables(self):\n", + " \"\"\"\n", + " Initialise all of the experiment variables used in results\n", + " collection. This method is called at the start of each run\n", + " of the model\n", + " \"\"\"\n", + " # variable used to store results of experiment\n", + " self.results = {}\n", + " self.results[\"n_shoulders\"] = 0\n", + " self.results[\"n_hips\"] = 0\n", + " self.results[\"n_wrists\"] = 0\n", + " self.results[\"n_ankles\"] = 0" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ad45a486-1042-4afd-bb7e-2b4e19cc2411", + "metadata": {}, + "outputs": [], + "source": [ + "def trauma_generator(env, trauma_type, args):\n", + " \"\"\"\n", + " Modified generator for arrivals.\n", + " Now works across all trauma types.\n", + "\n", + " Parameters:\n", + " ------\n", + " env: simpy.Environment\n", + " The simpy environment for the simulation\n", + "\n", + " trauma_type: str\n", + " string representing the type of patient e.g. \"shoulder\"\n", + "\n", + " args: Experiment\n", + " The settings and input parameters for the simulation.\n", + " \"\"\"\n", + " # use itertools as it provides an infinite loop\n", + " # with a counter variable that we can use for unique Ids\n", + " for patient_count in itertools.count(start=1):\n", + "\n", + " # the sample distribution is defined by the experiment.\n", + " inter_arrival_time = args.dists[trauma_type].sample()\n", + " yield env.timeout(inter_arrival_time)\n", + " \n", + " args.results[f\"n_{trauma_type}s\"] = patient_count\n", + " trace(f\"{env.now:.2f}: {trauma_type.upper()} arrival.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "c156a73e-9cfc-49d5-89ac-f8356e122cb5", + "metadata": {}, + "outputs": [], + "source": [ + "def single_run(\n", + " experiment, \n", + " rep=0,\n", + " run_length=RUN_LENGTH\n", + "):\n", + " \"\"\"\n", + " Perform a single run of the model and return the results\n", + "\n", + " Parameters:\n", + " -----------\n", + "\n", + " experiment: Experiment\n", + " The experiment/paramaters to use with model\n", + "\n", + " rep: int\n", + " The replication number.\n", + "\n", + " rc_period: float, optional (default=RUN_LENGTH)\n", + " The run length of the model\n", + " \"\"\"\n", + "\n", + " # reset all results variables to zero and empty\n", + " experiment.init_results_variables()\n", + "\n", + " # set random number set to the replication no.\n", + " # this controls sampling for the run.\n", + " experiment.set_random_no_set(rep)\n", + "\n", + " # environment is (re)created inside single run\n", + " env = simpy.Environment()\n", + "\n", + " # we pass all arrival generators to simpy \n", + " for trauma_type in experiment.dists.keys():\n", + " env.process(trauma_generator(env, trauma_type, experiment))\n", + "\n", + " # run for warm-up + results collection period\n", + " env.run(until=run_length)\n", + "\n", + " # return the count of the arrivals\n", + " return experiment.results" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "54c91312-5940-462f-a2d3-dd871a4f7a91", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'n_shoulders': 9, 'n_hips': 6, 'n_wrists': 14, 'n_ankles': 14}" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "TRACE = False\n", + "experiment = Experiment()\n", + "results = single_run(experiment)\n", + "results" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "fd02471e-903b-4064-a6c6-e304231cd7a8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'n_shoulders': 0, 'n_hips': 6, 'n_wrists': 0, 'n_ankles': 0}" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "M = 1_000_000\n", + "experiment = Experiment(iat_shoulder=M, iat_wrist=M, iat_ankle=M)\n", + "results = single_run(experiment)\n", + "results" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/content/img/multiple_arrivals.png b/content/img/multiple_arrivals.png new file mode 100644 index 0000000..bb2320b Binary files /dev/null and b/content/img/multiple_arrivals.png differ