{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "24d05ce9-3204-4442-b896-38cc4f7c373d",
   "metadata": {},
   "outputs": [],
   "source": [
    "### Pseudorandom Number Generator by Osvaldas Stanelis ###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "b5ea291b-9fcd-48cf-b065-f30532dd4450",
   "metadata": {},
   "outputs": [],
   "source": [
    "import random                     # Imports Python's non-cryptographic random module\n",
    "import secrets                    # Imports the cryptographically secure random module\n",
    "import numpy as np               # NumPy for numerical computations (e.g., mean)\n",
    "from statsmodels.sandbox.stats.runs import runstest_1samp  # Function for performing the Runs test\n",
    "from scipy.stats import chisquare # Function for performing the Chi-squared test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1db5f28f-783d-4aae-b15e-59a657e8a41e",
   "metadata": {},
   "outputs": [],
   "source": [
    "### Random integers generator ###\n",
    "def generate_random_integers_random_module(n, lower_bound, upper_bound):\n",
    "    \"\"\"\n",
    "    Generates `n` random integers using the `random` module.\n",
    "    Parameters:\n",
    "        n (int): Number of random integers to generate.\n",
    "        lower_bound (int): The smallest possible integer in the range.\n",
    "        upper_bound (int): The largest possible integer in the range.\n",
    "    Returns:\n",
    "        A list of random integers.\n",
    "    \"\"\"\n",
    "    random_integers = [] # Initializes an empty list to store generated integers\n",
    "    for _ in range(100): # Iterates n times to generate n random integers (in this case using 100)\n",
    "        rand_int = random.randint(lower_bound, upper_bound) # Uses random.randint() to generate an integer between lower_bound and upper_bound(inclusive)\n",
    "        random_integers.append(rand_int) # Appends the generated integer to the list\n",
    "    return random_integers # Returns the list of random integers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "a8f86758-b30c-4fd5-bfd3-d1e56a79aeda",
   "metadata": {},
   "outputs": [],
   "source": [
    "### Cryptographically secure random generator ###\n",
    "def generate_random_integers_secrets(n, lower_bound, upper_bound):\n",
    "    \"\"\"\n",
    "    Generates `n` cryptographically secure random integers using the `secrets` module.\n",
    "    Parameters:\n",
    "        n (int): Number of random integers to generate.\n",
    "        lower_bound (int): The smallest possible integer in the range.\n",
    "        upper_bound (int): The largest possible integer in the range.\n",
    "    Returns:\n",
    "        list: A list of cryptographically secure random integers.\n",
    "    \"\"\"\n",
    "    random_integers = [] # Initializes an empty list to store cryptographically secure random integers\n",
    "    for _ in range(100): # Iterates n times to generate n random integers (in this case using 100)\n",
    "        rand_int = lower_bound + secrets.randbelow(upper_bound - lower_bound + 1)\n",
    "        # secrets.randbelow(x) returns an integer in [0, x), so we offset it by lower_bound to ensure the value is within [lower_bound, upper_bound].\n",
    "        random_integers.append(rand_int) # Appends the cryptographically secure integer to the list\n",
    "    return random_integers # Returns the list of cryptographically secure random integers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "8a923782-4374-4978-9f43-d1d19b675408",
   "metadata": {},
   "outputs": [],
   "source": [
    "### Run test ###\n",
    "def runs_test(data):\n",
    "    \"\"\"\n",
    "    Performs a Runs test to check the randomness of a sequence. It converts\n",
    "    each value to 1 if >= mean, otherwise 0, then uses runstest_1samp.\n",
    "    Parameters:\n",
    "        data (list): Sequence of integer values to test.\n",
    "    Returns:\n",
    "        (float, float): A tuple containing the Z-statistic and the p-value.\n",
    "    \"\"\"\n",
    "    mean_val = np.mean(data) # Compute mean of the data\n",
    "    binary_seq = [1 if x >= mean_val else 0 for x in data] # Convert each value to 1 or 0 depending on whether it's above/equal-to or below the mean\n",
    "    z, p_value = runstest_1samp(binary_seq) # Perform the Runs test on the binary sequence\n",
    "    return z, p_value # Return the test statistic and p-value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "83c67ff5-83e8-4edf-8882-636b32dbba38",
   "metadata": {},
   "outputs": [],
   "source": [
    "### Chi-square test ###\n",
    "def chi_square_test(data, low, high):\n",
    "    \"\"\"\n",
    "    Performs a Chi-squared test to check whether the distribution\n",
    "    of integers is uniform over [low, high].\n",
    "    Parameters:\n",
    "        data (list): List of integers to test.\n",
    "        low (int): Minimum integer in the range (inclusive).\n",
    "        high (int): Maximum integer in the range (inclusive).\n",
    "    Returns:\n",
    "        (float, float): A tuple containing the Chi-squared statistic and the p-value.\n",
    "    \"\"\"\n",
    "    bins = high - low + 1 # Number of distinct values in [low, high]\n",
    "    freq = [0] * bins # Initialize frequency array for each possible value\n",
    "    for val in data: # Count how many times each integer appears\n",
    "        freq[val - low] += 1\n",
    "    chi_stat, p_value = chisquare(freq) # Perform the Chi-squared test on observed frequencies\n",
    "    return chi_stat, p_value # Return the chi-squared statistic and p-value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "4c3cdf1f-9b8e-454c-aa9a-06ff506d22a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "### Main function ###\n",
    "def main():\n",
    "    # Define the parameters for random number generation and tests\n",
    "    n = 100  # Number of random integers\n",
    "    low = 1  # Lower bound of the range\n",
    "    high = 500  # Upper bound of the range    \n",
    "    print(\"Generating and testing random data from two sources:\\n\")\n",
    "\n",
    "    # 1) Generating integers using the 'random' module\n",
    "    # ------------------------------------------------------------------\n",
    "    data_random = generate_random_integers_random_module(n, low, high)\n",
    "    \n",
    "    print(\"=== Random Module ===\")\n",
    "    print(f\"Generated Random Numbers:\\n{data_random}\\n\")  # NEW: Printing random numbers\n",
    "\n",
    "    # Runs test\n",
    "    z_random, p_runs_random = runs_test(data_random)\n",
    "    # Chi-squared test\n",
    "    chi_random, p_chi_random = chi_square_test(data_random, low, high)\n",
    "\n",
    "    print(f\"Number of samples: {n}, Range: [{low}, {high}]\")\n",
    "    print(f\"Runs Test -> Z-statistic: {z_random:.4f}, p-value: {p_runs_random:.4f}\")\n",
    "    if p_runs_random < 0.05:\n",
    "        print(\"   => Sequence may not be random (reject H0 at 5% level).\")\n",
    "    else:\n",
    "        print(\"   => Sequence appears random (fail to reject H0).\")\n",
    "\n",
    "    print(f\"Chi-Squared Test -> Chi-stat: {chi_random:.4f}, p-value: {p_chi_random:.4f}\")\n",
    "    if p_chi_random < 0.05:\n",
    "        print(\"   => Distribution may not be uniform (reject H0 at 5% level).\")\n",
    "    else:\n",
    "        print(\"   => Distribution appears uniform (fail to reject H0).\")\n",
    "\n",
    "    # ------------------------------------------------------------------\n",
    "    # 2) Generating integers using the 'secrets' module (crypto-secure)\n",
    "    # ------------------------------------------------------------------\n",
    "    data_secrets = generate_random_integers_secrets(n, low, high)\n",
    "\n",
    "    print(\"\\n=== Secrets Module ===\")\n",
    "    print(f\"Generated Secure Random Numbers:\\n{data_secrets}\\n\")  # NEW: Printing secure random numbers\n",
    "\n",
    "    # Runs test\n",
    "    z_secrets, p_runs_secrets = runs_test(data_secrets)\n",
    "    # Chi-squared test\n",
    "    chi_secrets, p_chi_secrets = chi_square_test(data_secrets, low, high)\n",
    "\n",
    "    print(f\"Number of samples: {n}, Range: [{low}, {high}]\")\n",
    "    print(f\"Runs Test -> Z-statistic: {z_secrets:.4f}, p-value: {p_runs_secrets:.4f}\")\n",
    "    if p_runs_secrets < 0.05:\n",
    "        print(\"   => Sequence may not be random (reject H0 at 5% level).\")\n",
    "    else:\n",
    "        print(\"   => Sequence appears random (fail to reject H0).\")\n",
    "\n",
    "    print(f\"Chi-Squared Test -> Chi-stat: {chi_secrets:.4f}, p-value: {p_chi_secrets:.4f}\")\n",
    "    if p_chi_secrets < 0.05:\n",
    "        print(\"   => Distribution may not be uniform (reject H0 at 5% level).\")\n",
    "    else:\n",
    "        print(\"   => Distribution appears uniform (fail to reject H0).\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "28654e58-73b1-4d92-b11d-7d5b285de948",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Generating and testing random data from two sources:\n",
      "\n",
      "=== Random Module ===\n",
      "Generated Random Numbers:\n",
      "[76, 36, 152, 398, 217, 94, 386, 196, 406, 292, 257, 17, 248, 55, 326, 290, 144, 306, 406, 223, 169, 436, 378, 134, 66, 28, 136, 380, 472, 372, 111, 231, 199, 307, 329, 407, 229, 307, 275, 158, 256, 333, 348, 96, 220, 55, 484, 431, 322, 289, 151, 234, 151, 480, 64, 467, 411, 286, 248, 359, 218, 189, 146, 355, 89, 200, 8, 251, 485, 2, 1, 9, 441, 263, 356, 439, 419, 497, 328, 389, 16, 465, 267, 313, 372, 409, 146, 331, 153, 361, 336, 60, 7, 173, 17, 52, 497, 50, 327, 310]\n",
      "\n",
      "Number of samples: 100, Range: [1, 500]\n",
      "Runs Test -> Z-statistic: -1.3759, p-value: 0.1688\n",
      "   => Sequence appears random (fail to reject H0).\n",
      "Chi-Squared Test -> Chi-stat: 490.0000, p-value: 0.6047\n",
      "   => Distribution appears uniform (fail to reject H0).\n",
      "\n",
      "=== Secrets Module ===\n",
      "Generated Secure Random Numbers:\n",
      "[102, 175, 450, 363, 425, 362, 293, 251, 408, 479, 379, 62, 254, 235, 105, 250, 266, 452, 329, 209, 292, 235, 309, 435, 488, 120, 456, 213, 40, 444, 434, 487, 86, 224, 274, 241, 363, 212, 432, 492, 68, 345, 92, 34, 16, 151, 82, 50, 17, 1, 221, 255, 361, 271, 397, 58, 358, 321, 487, 112, 152, 311, 408, 308, 435, 287, 63, 30, 178, 358, 290, 293, 405, 167, 390, 97, 461, 162, 232, 135, 68, 95, 55, 20, 118, 30, 134, 75, 49, 348, 143, 203, 440, 427, 92, 192, 461, 79, 493, 313]\n",
      "\n",
      "Number of samples: 100, Range: [1, 500]\n",
      "Runs Test -> Z-statistic: -1.8059, p-value: 0.0709\n",
      "   => Sequence appears random (fail to reject H0).\n",
      "Chi-Squared Test -> Chi-stat: 510.0000, p-value: 0.3569\n",
      "   => Distribution appears uniform (fail to reject H0).\n"
     ]
    }
   ],
   "source": [
    "# Entry point: only run main() if this script is executed (not imported)\n",
    "if __name__ == \"__main__\":\n",
    "    main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c29d1d6-d896-4a38-b66e-d62d994ef77b",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "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.12.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
