{ "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 }