{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a398cc49",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Week I - Key Python Features for Coding Numerical Simulations"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3b25ff85",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "\n",
    "## Dynamic typing\n",
    "\n",
    "Python is dynamically typed (opposed to static language such as C, C++, Fortran), hence:\n",
    "\n",
    "* Types are set on the values, not on the names, of the variables.\n",
    "* Types do not need to be known before variables are actually used: variable types are checked at run time!\n",
    "* Variable names can change types if their values are changed\n",
    "\n",
    "### Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "31e48d1a",
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "def inspect(x):\n",
    "    print('This object is: {}'.format(type(x)))\n",
    "    print('x = {}'.format(x))\n",
    "    return type(x)\n",
    "\n",
    "x = 2 \n",
    "inspect(x)\n",
    "\n",
    "x = 3.2\n",
    "inspect(x)\n",
    "\n",
    "x = 'Hello!'\n",
    "inspect(x)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d367e84",
   "metadata": {},
   "source": [
    "\n",
    "## Shared references\n",
    "\n",
    "Python is *reference counted*, so variables names are just references to the underlying values. How many times a reference is used and what are its related names is counted internally.\n",
    "Notably, multiple names can reference to the same object: **shared references**\n",
    "\n",
    "### Examples\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bec4246d",
   "metadata": {},
   "outputs": [],
   "source": [
    "x = 5\n",
    "y = x\n",
    "print(id(x))\n",
    "print(id(y))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "200cf2c6",
   "metadata": {},
   "outputs": [],
   "source": [
    "help(id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7c3cf0da",
   "metadata": {},
   "outputs": [],
   "source": [
    "x = 3\n",
    "print(id(x))\n",
    "print(id(y))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "70780769",
   "metadata": {},
   "outputs": [],
   "source": [
    "y = 4\n",
    "print(id(x))\n",
    "print(id(y))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7e674fbb",
   "metadata": {},
   "outputs": [],
   "source": [
    "x = ['a','b','c']\n",
    "y = x\n",
    "print(id(x))\n",
    "print(id(y))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ebe8fe75",
   "metadata": {},
   "outputs": [],
   "source": [
    "x[1] = 'w'\n",
    "print(x)\n",
    "print(y)\n",
    "print(id(x),id(y))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e3fbef4",
   "metadata": {},
   "source": [
    "## Mutability\n",
    "\n",
    "In Python, data types can be either mutable or immutable:\n",
    "\n",
    "* **mutable** types allow values to change after creation, such as *lists, dictionaries, sets*\n",
    "* **immutable** types are static and cannot change values, such as *int, float, bool, str, tuples*\n",
    "\n",
    "### Examples"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e9e2d5e7",
   "metadata": {},
   "outputs": [],
   "source": [
    "lis = ['a','b','c']\n",
    "tup = ('a','b','c')\n",
    "print(lis)\n",
    "print(id(lis),id(tup))\n",
    "lis[2] = 'zz'\n",
    "print(lis)\n",
    "print(id(lis))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eba371b0",
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    tup[2] = 'zz'\n",
    "except TypeError as err:\n",
    "    print('Forbidden!',err)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4ad9adbc",
   "metadata": {},
   "source": [
    "\n",
    "## Integers and Floats in Python\n",
    "\n",
    "Several numerical datatypes are available in Python, such as:\n",
    "* *int*: integers, with unlimited precision!\n",
    "* *float*: floating points, (usually) implemented with 'double' in C (check with sys.float_info)\n",
    "* *complex*: two floats (get with z.real and z.imag)\n",
    "\n",
    "Other relevant numeric datatypes:\n",
    "* *fractions.Fraction*, rational number arithmetics \n",
    "* *decimal.Decimal*, floats with user-definable precision\n",
    "\n",
    "Numpy has its own datatypes, here some of the most relevant:\n",
    "* np.half, np.single, np.double, np.longdouble (depends on platform!)\n",
    "* np.float16 (alias of np.half), np.float32 (alias of np.single), np.float64 (alias of np.double), np.float128 (alias np.longdouble - existence depends on the platform!)\n",
    "\n",
    "For more details on extended precision, beyond 64 bits, check out https://numpy.org/doc/stable/user/basics.types.html#extended-precision"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e620a8e6",
   "metadata": {},
   "source": [
    "-------------"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec8f18da",
   "metadata": {},
   "source": [
    "\n",
    "### Week 1, Exercise - Underflow, overflow and machine precision\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "330ee8d6",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "def check_underflow_limit(float_type):\n",
    "    '''\n",
    "    Returns the underflow limit\n",
    "    '''\n",
    "    a = float_type(1.0)\n",
    "    while a>float_type(0.0):\n",
    "        under = a\n",
    "        a = a/float_type(2.0)\n",
    "    print('The calculated underflow limit is:{}'.format(under))\n",
    "\n",
    "def check_overflow_limit(float_type):\n",
    "    '''\n",
    "    Returns the overflow limit\n",
    "    '''\n",
    "    a = float_type(1.0)\n",
    "    while a!=float_type('Inf'):\n",
    "        over = a\n",
    "        a = a*float_type(2.0)\n",
    "    print('The calculated overflow limit is:{}'.format(over))\n",
    "\n",
    "def check_machine_precision(float_type):\n",
    "    '''\n",
    "    Returns machine precision\n",
    "    '''\n",
    "    eps = float_type(1.0)\n",
    "    while (float_type(1.0)+eps!=float_type(1.0)):\n",
    "        eps = eps/float_type(2.0)\n",
    "    print('The calcuated machine precision is:{}'.format(float_type(2)*eps))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "121931e7",
   "metadata": {},
   "outputs": [],
   "source": [
    "for ft in [float,np.float16,np.float32,np.float64,np.longdouble]:\n",
    "    print(str(ft))\n",
    "    check_underflow_limit(ft)\n",
    "    check_overflow_limit(ft)\n",
    "    check_machine_precision(ft)\n",
    "    print('---------------')\n",
    "    #Compare with numpy results\n",
    "    print(np.finfo(ft))"
   ]
  }
 ],
 "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
}