{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "44056652",
   "metadata": {},
   "source": [
    "# Week IV - Shallow VS Deep Copy, Radioactive Decay and RNGs"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16440556",
   "metadata": {},
   "source": [
    "### REMINDER\n",
    "\n",
    "* You are encouraged to use the Ubuntu virtual machines also from home, see dedicated instruction on Moodle (Week I)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d4b5125",
   "metadata": {},
   "source": [
    "## Deep and Shallow Copies"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "409c4bd6",
   "metadata": {},
   "source": [
    "In Python, copies are shallow by default.\n",
    "\n",
    "> A **shallow copy** of an object shares references that are attributes of the original object (L. Ramalho, *Fluent Python*, O'Reilly, 2016)\n",
    "\n",
    "The easiest way to perform a shallow copy of an object is to use the built-in constructor for the type itself."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e77ec873",
   "metadata": {},
   "outputs": [],
   "source": [
    "l_1 = [3,[55,33],(9,8,7)]\n",
    "l_2 = l_1\n",
    "\n",
    "def check_lists(l_1,l_2):\n",
    "    obj = l_2 is l_1\n",
    "    value = (l_2==l_1)\n",
    "    print('Same object:',obj)\n",
    "    print('Equal value:',value)\n",
    "    return\n",
    "\n",
    "print('l_2 = l_1')\n",
    "check_lists(l_1,l_2)\n",
    "print('-'*10)\n",
    "print('l_2 = list(l_1)')\n",
    "l_2 = list(l_1)\n",
    "check_lists(l_1,l_2)\n",
    "print('-'*10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "730277df",
   "metadata": {},
   "outputs": [],
   "source": [
    "l_2 = list(l_1)\n",
    "print('l_1.append(42)')\n",
    "l_1.append(42)\n",
    "check_lists(l_1,l_2)\n",
    "print(l_1)\n",
    "print(l_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eebc37e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "print('l_1.remove(3)')\n",
    "l_1[1].remove(33)\n",
    "check_lists(l_1,l_2)\n",
    "print(l_1)\n",
    "print(l_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "123a7d57",
   "metadata": {},
   "outputs": [],
   "source": [
    "print('l_2[1] += [1,2]; l_2[2] += (3,4)')\n",
    "l_2[1] += [1,2]\n",
    "l_2[2] += (3,4)\n",
    "check_lists(l_1,l_2)\n",
    "print(l_1)\n",
    "print(l_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f4bd149",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%html\n",
    "<iframe width=\"800\" height=\"500\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=l_1%20%3D%20%5B3,%5B55,33%5D,%289,8,7%29%5D%0Al_2%20%3D%20l_1%0Al_2%20%3D%20list%28l_1%29%0Al_2%20%3D%20list%28l_1%29%0Al_1.append%2842%29%0Al_1%5B1%5D.remove%2833%29%0Al_2%5B1%5D%20%2B%3D%20%5B1,2%5D%0Al_2%5B2%5D%20%2B%3D%20%283,4%29%0A&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=nevernest&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false\"> </iframe>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a51e6cb",
   "metadata": {},
   "source": [
    "Permanent link at \n",
    "https://pythontutor.com/visualize.html#code=l_1%20%3D%20%5B3,%5B55,33%5D,%289,8,7%29%5D%0Al_2%20%3D%20l_1%0Al_2%20%3D%20list%28l_1%29%0Al_2%20%3D%20list%28l_1%29%0Al_1.append%2842%29%0Al_1%5B1%5D.remove%2833%29%0Al_2%5B1%5D%20%2B%3D%20%5B1,2%5D%0Al_2%5B2%5D%20%2B%3D%20%283,4%29%0A&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7ff42575",
   "metadata": {},
   "source": [
    "The ``copy`` module provides functions to perform:\n",
    "* shallow copies with ``copy.copy``\n",
    "* deep copies with ``copy.deepcopy``\n",
    "\n",
    "> A **deep copy** is a duplicate which does not share references of embedded objects."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eb01042e",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%html\n",
    "<iframe width=\"800\" height=\"500\" frameborder=\"0\" src=\"https://pythontutor.com/iframe-embed.html#code=import%20copy%0Al_1%20%3D%20%5B3,%5B55,33%5D,%289,8,7%29%5D%0Asl_2%20%3D%20copy.copy%28l_1%29%0Adl_2%20%3D%20copy.deepcopy%28l_1%29%0Al_1%5B1%5D.append%2827%29%0Al_1%5B2%5D%20%2B%3D%20%28333,111%29&codeDivHeight=400&codeDivWidth=350&cumulative=false&curInstr=0&heapPrimitives=nevernest&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false\"> </iframe>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f11af636",
   "metadata": {},
   "source": [
    "Permanent link at https://pythontutor.com/visualize.html#code=import%20copy%0Al_1%20%3D%20%5B3,%5B55,33%5D,%289,8,7%29%5D%0Asl_2%20%3D%20copy.copy%28l_1%29%0Adl_2%20%3D%20copy.deepcopy%28l_1%29%0Al_1%5B1%5D.append%2827%29%0Al_1%5B2%5D%20%2B%3D%20%28333,111%29&cumulative=false&curInstr=0&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=%5B%5D&textReferences=false"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "695aef0a",
   "metadata": {},
   "source": [
    "----------------------------------------"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9b494a8e",
   "metadata": {},
   "source": [
    "## Exercise - radioactive decay\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8f25380",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "class radio_decay:\n",
    "    '''\n",
    "    This class simulates the radioactive decay\n",
    "    of N isotopes\n",
    "    '''\n",
    "    \n",
    "    def __init__(self,N_init,lamb):\n",
    "        'Initialise the process'\n",
    "        self.N_init = N_init\n",
    "        self.lamb = lamb\n",
    "        self.N = N_init\n",
    "        self.rng = np.random.default_rng(seed=42424)\n",
    "        self.step_counter = 0\n",
    "        self.all_N = [N_init]\n",
    "        print('Initial number of particles: {}, decay constant: {}'.format(self.N_init,self.lamb))\n",
    "    \n",
    "    def run_single_step(self):\n",
    "        'Run a single random step for N particles'\n",
    "        r = self.rng.random(self.N)\n",
    "        self.N = self.N - sum(map(lambda x: True if (x <= self.lamb) else False, r))\n",
    "        \n",
    "    def run_steps(self,n_steps):\n",
    "        'Run a number of random steps equal to n_steps'\n",
    "        for i in range(n_steps):\n",
    "            self.step_counter += 1\n",
    "            print('Running step # {}'.format(self.step_counter))\n",
    "            self.run_single_step()\n",
    "            print('Number of particles: {}'.format(self.N))\n",
    "            self.all_N.append(self.N)\n",
    "    \n",
    "    def visualize(self,logplot=False):\n",
    "        if logplot:\n",
    "            plt.semilogy(range(len(self.all_N)),self.all_N,'o-')\n",
    "        else:\n",
    "            plt.plot(range(len(self.all_N)),self.all_N,'o-')\n",
    "        plt.xlabel('t')\n",
    "        plt.ylabel('N')\n",
    "        plt.show()\n",
    "        \n",
    "    def status(self):\n",
    "        print('Current number of particles: {}, after {} steps'.format(self.N,self.step_counter))\n",
    "        "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "35ec1ca3",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Let's create an instance of the class\n",
    "rd = radio_decay(10000,0.1)\n",
    "#and evolve it for ten steps\n",
    "rd.run_steps(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0177033b",
   "metadata": {},
   "outputs": [],
   "source": [
    "#We inspect the status\n",
    "rd.status()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "396cca04",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Here we visualize data\n",
    "rd.visualize()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "54a77635",
   "metadata": {},
   "outputs": [],
   "source": [
    "#Homework: try to fit the exponential and extract lambda\n",
    "import matplotlib.pyplot as plt\n",
    "plt.semilogy(range(len(rd.all_N)),rd.all_N,'o-')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8892d361",
   "metadata": {},
   "source": [
    "----------------------------------------"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b500a4de",
   "metadata": {},
   "source": [
    "## Algorithms and generators for random numbers \n",
    "\n",
    "* Nowadays, it is generally recommended **not** to use the global Numpy random number generator (RNG), i.e. ``np.random.rand`` and ``np.random.seed``. \n",
    "\n",
    "* Instead, one should create an instance of a specific RNG and specify its seed.\n",
    "\n",
    "* Numpy has several algorithms already implemented, the default ``np.random.default_rng`` being *PCG64*: https://www.pcg-random.org/paper.html ; https://www.pcg-random.org .\n",
    "\n",
    "> The generation of random bits and the production of probability distributions, check out Numpy documentation at https://numpy.org/doc/stable/reference/random/index.html#module-numpy.random :\n",
    "> * **BitGenerators**: Objects that generate random numbers. \n",
    "> * **Generators**: Objects that transform sequences of random bits from a BitGenerator into sequences of numbers that follow a specific probability distribution (such as uniform, Normal or Binomial) within a specified interval."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d0372f8b",
   "metadata": {},
   "outputs": [],
   "source": [
    "#MT19937, Philox, PCG64, PCG64DXSM, SFC64\n",
    "print(np.random.MT19937)\n",
    "np.random.MT19937"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6e786969",
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.default_rng??"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f2136ecf",
   "metadata": {},
   "outputs": [],
   "source": [
    "np.random.PCG64?"
   ]
  }
 ],
 "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.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}