├── .gitignore
├── requirements.txt
├── differential.png
├── Licence.md
├── Numerical Computing Python Slides.pptx
├── What are NumPy and SciPy.ipynb
├── Integration.ipynb
├── Other Features.ipynb
├── differential.svg
├── Built-In Functions.ipynb
├── README.md
├── Performance Comparison.ipynb
├── Initial Value Problems.ipynb
├── Creating and Manipulating NumPy Arrays.ipynb
├── Projects.ipynb
└── Array Operations.ipynb
/.gitignore:
--------------------------------------------------------------------------------
1 | /.ipynb_checkpoints
2 | ~$*
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy
2 | scipy
3 | matplotlib
--------------------------------------------------------------------------------
/differential.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ImperialCollegeLondon/RCDS-numerical-computing-in-python-with-numpy-and-scipy/HEAD/differential.png
--------------------------------------------------------------------------------
/Licence.md:
--------------------------------------------------------------------------------
1 | Distributed under [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode)
2 |
--------------------------------------------------------------------------------
/Numerical Computing Python Slides.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ImperialCollegeLondon/RCDS-numerical-computing-in-python-with-numpy-and-scipy/HEAD/Numerical Computing Python Slides.pptx
--------------------------------------------------------------------------------
/What are NumPy and SciPy.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "id": "LJxGI53ggMoG"
7 | },
8 | "source": [
9 | "# What are NumPy and SciPy?\n",
10 | "\n",
11 | "[NumPy](https://numpy.org/) and [SciPy](https://www.scipy.org/) are two of the most commonly used modules in Python. They contain functionality which allows large amounts of data to be handled easily and efficiently and provided functions to allow complex mathematical equations to be solved quickly, in terms of both coding time and running time.\n",
12 | "\n",
13 | "These modules are helpful because they give access to complex code designed by very competent coders to achieve particular goals. This allows you to include well-made code into your projects without writing it yourself. This can save a large amount of time compared to writing code yourself to produce the same results.\n",
14 | "\n",
15 | "In addition, the code used in NumPy contains a lot of code written in C and SciPy contains a lot of code written in C, C++ and Fortran. All of these languages are significantly more efficient that Python. The complexities of the interfaces between these languages if hidden inside the modules and the use of these languages means that using NumPy and SciPy will produce much better performance than even expertly written Python.\n",
16 | "\n",
17 | "For these reasons, NumPy and SciPy are vital and common tools in the toolkit of Python-based numerical programmer."
18 | ]
19 | }
20 | ],
21 | "metadata": {
22 | "colab": {
23 | "authorship_tag": "ABX9TyO50FYzmnH+CYpFFDCfPFER",
24 | "name": "What are NumPy and SciPy.ipynb",
25 | "provenance": []
26 | },
27 | "kernelspec": {
28 | "display_name": "Python 3",
29 | "language": "python",
30 | "name": "python3"
31 | },
32 | "language_info": {
33 | "codemirror_mode": {
34 | "name": "ipython",
35 | "version": 3
36 | },
37 | "file_extension": ".py",
38 | "mimetype": "text/x-python",
39 | "name": "python",
40 | "nbconvert_exporter": "python",
41 | "pygments_lexer": "ipython3",
42 | "version": "3.8.5"
43 | }
44 | },
45 | "nbformat": 4,
46 | "nbformat_minor": 1
47 | }
48 |
--------------------------------------------------------------------------------
/Integration.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{"id":"hamwzo-3Uvuv"},"source":["# Integration\n","\n","Scipy contains a [suite of tools](https://docs.scipy.org/doc/scipy/reference/integrate.html) in the module ```scipy.integrate ``` which can be used to integrate functions. Let's look at the ```scipy.integrate.quad``` [function](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.quad.html#scipy.integrate.quad) as an example. We'll use the following function to demonstrate:\n","\n","$f(x) = x^{3} + 3x^{2} - 2x +1$\n","\n","When we integrate this, we obtain:\n","\n","$\\int f(x)\\textrm{d}x = \\frac{x^{4}}{4} + x^{3} - x^{2} + x + c$\n","\n","We can evaluate this integral over a fixed range and find:\n","\n","$\\int\\limits_{0}^{1} f(x)\\textrm{d}x = \\frac{5}{4}$\n","\n","We can call ```scipy.integrate.quad``` to evaluate this value. The first argument is the function (which must accept a single argument and return a scalar value). The second and third values are the upper and lower bounds of the definite integral."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"ytxfNg-KUtAv"},"outputs":[],"source":["from scipy.integrate import quad\n","\n","def sample_function(x):\n"," return x ** 3 + 3 * x ** 2 - 2 * x + 1\n","\n","print(quad(sample_function, 0, 1))"]},{"cell_type":"markdown","metadata":{"id":"a9hVMw42YgTb"},"source":["Here, ```quad``` has returned a tuple containing two values. The first of these is the value of integral as evaluated by ```quad```, the second is the estimate of the maximum error of the function."]},{"cell_type":"markdown","metadata":{"id":"HWsQAX-kZwtm"},"source":["## Exercise\n","\n","For the following functions:\n","\n","$ f(x) = \\sin(x),\\\\g(x) = x$\n","\n","calculate the following (correct values are given for you to check your results):\n","\n","$\n","\\int_{0}^{1}g(x)\\textrm{d}x = 0.5\\\\\n","\\int_{0}^{\\pi}\\left(f(x)+g(x)\\right)\\textrm{d}x \\approx 6.93\n","$\n","\n","For all cases, compare the value you calculate to the correct answers:"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"KIvR2jgMb4n6"},"outputs":[],"source":[]},{"cell_type":"code","execution_count":3,"metadata":{"cellView":"form","id":"9257kEdJb47J"},"outputs":[{"name":"stdout","output_type":"stream","text":["int g from 0 to 1: 0.5\n","int f+g from 0 to pi: 6.934802200544679\n","int f+g from 0 to pi: 6.934802200544679\n"]}],"source":["#@title\n","\n","import math\n","from scipy.integrate import quad\n","\n","def f(x):\n"," return math.sin(x)\n","\n","def g(x):\n"," return x\n","\n","# Note selecting the first element of the tuple to get just the answer\n","print(\"int g from 0 to 1: \", quad(g, 0, 1)[0])\n","\n","# Add together the (first element of the) results from two calls to quad\n","print(\"int f+g from 0 to pi: \", quad(f, 0, math.pi)[0] + quad(g, 0, math.pi)[0])\n","\n","# You could also have made a new function to return x + sin(x) and called quad once\n","def h(x):\n"," return f(x) + g(x)\n","\n","print(\"int f+g from 0 to pi: \", quad(h, 0, math.pi)[0])"]},{"cell_type":"markdown","metadata":{"id":"fSScCIxNXwfL"},"source":["## Additional Arguments\n","\n","```scipy.integrate.quad``` allows for functions which accept multiple arguments. The integration will be performed with respect to the first argument passed to the function being integrated. The values of other functions may be specified by writing ```args=``` and then a series of values in parentheses separated by commas.\n","\n","For example, in the cell below we differentiate and integrate a function which represents a quadratic polynomial."]},{"cell_type":"code","execution_count":2,"metadata":{"id":"ZWCSd-mMZ08B"},"outputs":[{"name":"stdout","output_type":"stream","text":["Integral 1: (8.333333333333334, 9.251858538542972e-14)\n","Integral 2: (5.0, 5.551115123125783e-14)\n"]}],"source":["from scipy.integrate import quad\n","\n","# Define a function to return a result of the form ax^2 + bx + c\n","def quadratic(x, a, b, c):\n"," return a * x ** 2 + b * x + c\n","\n","# Integrate the function with parameters a=1, b=2, c=3 between x=1 and x=2\n","print(\"Integral 1:\", quad(quadratic, 1, 2, args=(1,2,3)))\n","\n","# Integrate the function with parameters a=3, b=-2, c=5 between x=0 and x=1\n","print(\"Integral 2: \", quad(quadratic, 0, 1, args=(3,-2,5)))"]}],"metadata":{"colab":{"collapsed_sections":[],"name":"Integration.ipynb","provenance":[]},"kernelspec":{"display_name":"Python 3","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.5"}},"nbformat":4,"nbformat_minor":0}
2 |
--------------------------------------------------------------------------------
/Other Features.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{"id":"ou0fYTQbnfXm"},"source":["# Other Features\n","\n","This course has covered a small fraction of the feature of NumPy and SciPy, attempting to focus on what you're most likely to find useful and interesting. In many cases, there will be alternative ways to achieve some of the effects you've seen. If you see another way of doing something in someone else's code or find another way in the documentation, it is likely another valid solution and might even suit your needs better. So, as you experiment with these packages you'll probably develop a more personalised toolbox of functions you use regularly.\n","\n","This notebook takes a quick look at a number of features that you might want to add to your toolbox, without going into detail on each of them too much. If you're interested in a particular tool, read the linked documentation and experiment with them. The full list of features form NumPy can be found [here](https://numpy.org/doc/stable/reference/) and the full list of SciPy features can be found [here](https://docs.scipy.org/doc/scipy/reference/)."]},{"cell_type":"markdown","metadata":{"id":"b06jCYlQpCfN"},"source":["## Constants\n","\n","A large number of physical constants are included in SciPy in the ```scipy.constants``` [module](https://docs.scipy.org/doc/scipy/reference/constants.html). For instance:"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"iOljevVhnc7W"},"outputs":[],"source":["import scipy.constants as constants\n","\n","print(\"Speed of light = \", constants.c)\n","print(\"Avogadro's Number = \", constants.Avogadro)\n","print(\"Electron mass, unit and uncertainty: \", constants.physical_constants[\"electron mass\"])\n","print(\"Standard atmosphere pressure, unit and uncertainty: \", constants.physical_constants[\"standard atmosphere\"])"]},{"cell_type":"markdown","metadata":{"id":"gbdKs-b-u1Ak"},"source":["## Fourier Transforms\n","\n","A number of different functions which calculate Fourier transforms are found in the ```scipy.fft``` [module](https://docs.scipy.org/doc/scipy/reference/fft.html). For instance:"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"bWnpnPywpXo4"},"outputs":[],"source":["from scipy.fft import fft\n","import numpy as np\n","\n","x = np.array([1, 2, 0, 10, 3, 1, -1, -5])\n","\n","# The terms are the intensities of frequencies k/n where n is the number of points provided (8) and k is the index of the returned array\n","print(fft(x))"]},{"cell_type":"markdown","metadata":{"id":"SQV6szRH2QSa"},"source":["## Statistics\n","\n","A number of different statistical functions, include representations of many Probability Density Functions (PDFs) are found in the ```scipy.stats``` [module](https://docs.scipy.org/doc/scipy/reference/stats.html). For instance:"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"rf_v2o6xvj8W"},"outputs":[],"source":["from scipy.stats import norm\n","import matplotlib.pyplot as plt\n","import numpy as np\n","\n","# Create the Gaussian\n","gaussian = norm(10, 2)\n","\n","#Create a plot of the Gaussian\n","x = np.arange(5, 15, 0.1)\n","y = gaussian.pdf(x)\n","fig, ax = plt.subplots(1, 1)\n","ax.plot(x, y)\n","\n","# We can find the PDF at a given value\n","print(\"PDF at 8: \", gaussian.pdf(8))\n","\n","#We can find the cumulative density function at a given value\n","print(\"CDF at 12: \", gaussian.cdf(12))\n","\n","# Draw 10 random numbers from the PDF\n","print(gaussian.rvs(size=10))"]},{"cell_type":"markdown","metadata":{"id":"0qeK_2gDTp5J"},"source":["## Root Finding and Optimisation\n","\n","The ```scipy.optimize``` module contains many functions relating to finding minima or roots of a function. For instance:"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"CtjmphunQ0fp"},"outputs":[],"source":["import math\n","from scipy.optimize import minimize_scalar, root_scalar\n","\n","def sample_function(x):\n"," return (np.sin(x) + 1) * (x - 2) * (x + 4)\n","\n","# Plot the sample function\n","x = np.arange(-10, 10, 0.1)\n","y = sample_function(x)\n","fig, ax = plt.subplots(1, 1)\n","ax.plot(x, y)\n","\n","#Find the minimum of the function\n","print(\"Minimum\")\n","print(minimize_scalar(sample_function))\n","\n","#Find the root of the function (i.e. where sample_function returns 0) between 1 and 3\n","print(\"Roots\")\n","print(root_scalar(sample_function, method=\"bisect\", bracket=(1,3)))"]}],"metadata":{"colab":{"collapsed_sections":[],"name":"Other Features.ipynb","provenance":[]},"kernelspec":{"display_name":"Python 3","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.8.5"}},"nbformat":4,"nbformat_minor":0}
2 |
--------------------------------------------------------------------------------
/differential.svg:
--------------------------------------------------------------------------------
1 |
2 |
139 |
--------------------------------------------------------------------------------
/Built-In Functions.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{"id":"h8bitxxIyp6H"},"source":["# Built-In Functions\n","\n","```scipy.special``` contains a number of built in functions that may be useful for different applications. A summary of these can be found [here](https://docs.scipy.org/doc/scipy/reference/special.html). There are a few reasons you might choose to use the SciPy versions:\n","* You don't have to spend time writing it yourself\n","* The SciPy version will be well-tested and robust\n","* The Scipy version will work element-wise on arrays of data \n","* The SciPy version will be faster to run than what you could write yourself, particularly when operating on arrays of values\n","\n","Many of the functions in ```scipy.special``` may be applied element-wise to NumPy arrays.\n","\n","There is a lot of variation in the functions available in ```scipy.special``` and it's impossible to cover them all. Instead, this notebook runs through some examples that may be useful in themselves, but also aims to demonstrate the types of functions and interfaces which are available."]},{"cell_type":"markdown","metadata":{"id":"TWAnjBBPdWN9"},"source":["## Permutations\n","\n","The permutation function determines the number of different ways $k$ things can be taken from a pool of $n$ things. This is a common function in statistics and has the functional form:\n","\n","$P = \\frac{n!}{(n-k)!}$\n","\n","```scipy.special.perm``` [provides a way for calculating this value in SciPy](https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.perm.html#scipy.special.perm). The first argument is the value of $n$ and the second is the value of $k$."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"VKdJyjz1Wl5m"},"outputs":[],"source":["from scipy.special import perm\n","\n","print(\"n=3, k=2: \", perm(3,2))\n","print(\"n=100, k=10: \",perm(100,10))"]},{"cell_type":"markdown","metadata":{"id":"WQlcMF73HQh-"},"source":["## Error Function\n","\n","The error function ($erf$) is used to calculate probabilities drawn from a Normal distribution and is defined as:\n","\n","$erf(z) = \\frac{2}{\\sqrt{\\pi}}\\int_{0}^{z}e^{-t^{2}}\\textrm{d}t$\n","\n","```scipy.special.erf``` [contains an way to evaluate these functions using SciPy](https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.erf.html). The only argument is the value for which the error function is to be evaluated."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"cGHDRGJFRfZJ"},"outputs":[],"source":["from scipy.special import erf\n","\n","print(erf(1))"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"gd4EQPaYRlZI"},"outputs":[],"source":["# This example plots the values of erf as a function of x\n","# If you're not familiar with matplotlib, don't worry about that aspect of the code\n","\n","import matplotlib.pyplot as plt\n","from scipy.special import erf\n","import numpy as np\n","\n","x = np.arange(-3, 3.001, 0.1)\n","# We can calculate a NumPy array of results because erf acts element-wise on x\n","y = erf(x)\n","\n","plt.plot(x,y)"]},{"cell_type":"markdown","metadata":{"id":"ufG76FTp0PES"},"source":["## Spherical Harmonics\n","\n","[Spherical functions](https://en.wikipedia.org/wiki/Spherical_harmonics) are a type of function defined on a surface of a sphere that occur in the solution of partial differential equations in many scientific fields, including electric fields, quantum mechanics, geoids and gravitational fields. There are multiple different spherical harmonic functions of different \"order\" and \"degree\". The magnitude of the order of the function must be less than or equal to the degree of the function and the degree of the function must be zero or greater.\n","\n","```scipy.special.sph_harm``` [contains an way to evaluate these functions using SciPy](https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.sph_harm.html#scipy.special.sph_harm). The first value is the order of the spherical harmonic, the second is its degree and the third and fourth are the azimuthal and polar coordinates respectively at which the spherical harmonic is to be evaluated. The function will accept complex values and may give complex answers."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"Ts52mVrqyksa"},"outputs":[],"source":["from scipy.special import sph_harm\n","\n","print(sph_harm(1,3,0,1))"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"EbKvxlWXymhu"},"outputs":[],"source":["# This example plots the values of sph_harm as a function of x and y\n","# If you're not familiar with matplotlib, don't worry about that aspect of the code\n","\n","from scipy.special import sph_harm\n","import numpy as np\n","import matplotlib.pyplot as mpl\n","import math\n","\n","# Use various array manipulations and operations we've seen to create azimuthal and polar coordinates on a 200x100 2D grid\n","azimuthal = np.full([100, 200], 1) * np.arange(0, 2 * math.pi, math.pi/100).reshape(1, 200)\n","polar = np.arange(0, math.pi, math.pi/100).reshape(100, 1) * np.full([100, 200], 1)\n","\n","# Pass these 2D arrays to sph_harm\n","# sph_harm will return a 2D array of results\n","# Use the NumPy \"real\" function to select only the real components of the results\n","sph = np.real(sph_harm(2, 4, azimuthal, polar))\n","\n","# Plot the results using matplotlib\n","# Don't worry if you don't follow this code\n","mpl.imshow(sph, extent = (0, 2 *math.pi, 0, math.pi))\n","mpl.colorbar()"]},{"cell_type":"markdown","metadata":{"id":"AsJVtxMIb28p"},"source":["## Exercise\n","\n","Look through the [list of built-in functions](https://docs.scipy.org/doc/scipy/reference/special.html) in ```scipy.special```. Find one or two functions that are relevant to your research (or, at least, that you've heard of before) and have a go at using them in the cell below."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"VJoWtRqad4pR"},"outputs":[],"source":[]}],"metadata":{"colab":{"collapsed_sections":[],"name":"Built-In Functions.ipynb","provenance":[]},"kernelspec":{"display_name":"Python 3","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.5"}},"nbformat":4,"nbformat_minor":0}
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Numerical-Computing-in-Python-with-NumPy-and-SciPy
2 |
3 | ## Pre-Course Instructions
4 |
5 | ### Webinar Instructions
6 |
7 | To complete this course, you will need to run Jupyter Notebook files on your computer. The easiest way to do this is to ensure you have a Google account. This will allow us to use the online Google Colab tool to run the notebooks on the day.
8 |
9 | Alternatively, if you don’t have and don’t want to create a Google account, you can download and run the notebooks locally instead using whichever program you are most familiar and comfortable with. For instance, install Anaconda on your computer by downloading the appropriate distribution from [here](https://www.anaconda.com/distribution/), or install [Miniconda](https://docs.conda.io/en/latest/miniconda.html) and [install the package](https://cs205uiuc.github.io/guidebook/resources/python-miniconda.html) ```jupyter``` within it.
10 |
11 | The presentation and the Notebooks may be found [here](https://github.com/coolernato/Numerical-Computing-in-Python-with-NumPy-and-SciPy/archive/refs/heads/master.zip). You should download them before the course. However, you do not need to read them before attending.
12 |
13 | ### Face to Face Course
14 |
15 | This course will take place in an ICT computer room and so laptops are not required but you may bring one if you wish. Please make sure it is fully charged. This course will involve the running of Jupyter Notebook files. The easiest way to do this is to ensure you have a Google account. This will allow you to use the online Google Colab tool to run the notebooks on the day.
16 |
17 | Alternatively, if you don’t have and don’t want to create a Google account, Imperial computers (including those in the computer rooms) will have Anaconda available to them through the Software Hub which will also allow the running of Jupyter Notebooks. You may also chose to install Anaconda on your personal machine by downloading the appropriate distribution from [here](https://www.anaconda.com/distribution/).
18 |
19 | The presentation and the Notebooks may be found [here](https://github.com/coolernato/Numerical-Computing-in-Python-with-NumPy-and-SciPy/archive/refs/heads/master.zip) If you want to undertake the course on your own laptop, you may want to download them. However, you do not need to read them before attending.
20 |
21 | ### Self-Study Instructions
22 |
23 | This course can also be completed through self-study from this repository. To do this, you will need to run the Jupyter Notebooks on your computer. The easiest way to do this is to ensure you have a Google account and are logged in on the your browser. Then, you can follow the links in the "Colab Links" section of this file in order from top-to-bottom in order to work through the notebooks. You will need to create a copy of the notebooks by clicking File>Save a Copy in Drive. This will create a copy of the notebook in your Google Drive and allow you to edit it to add notes, run examples and complete exercises. Underneath the code cells left for you to complete each exercise, there will be a code cell saying "Show Code". Clicking on this will show a sample solution to check your answer.
24 |
25 | Alternatively, if you don’t have and don’t want to create a Google account, you may instead install Anaconda on your computer by downloading the appropriate distribution from [here](https://www.anaconda.com/distribution/). The presentation and the Notebooks may be downloaded [here](https://github.com/coolernato/Numerical-Computing-in-Python-with-NumPy-and-SciPy/archive/refs/heads/master.zip). You can then open the notebooks using Anaconda (open them in the order written in the "Colab Links" section below). You can edit these files to add notes, run examples or write your solutions to the exercises. Under the code cell left blank for you to write your solution to these exercises and the code cell below contains a sample solution for you to compare your answer to.
26 |
27 | ## Packages
28 |
29 | Whether you attend a face-to-face or webinar version of this course or if you intend to self-study, if you intend to run the code in Anaconda, install the following packages in your Python environment before you attend to avoid having to do so during the course:
30 |
31 | * numpy
32 | * scipy
33 | * matplotlib
34 |
35 | If you're familiar with how to do so, you can use the file ```requirements.txt``` in this repository to set up your environment.
36 |
37 | ## Colab Links
38 |
39 | The following are links to the Notebooks which will open in Google Colab. To use these links, you will need to log into a Google account. Once you click the link, you may see a page saying "Connected apps". If Google Colaboratory is in the list, click it to open the notebook. If it's not, click "Connect more apps...", search for "Colab" and connect the app before selecting it.
40 |
41 | * [What Are NumPy and SciPy]()
42 | * [Creating and Manipulating NumPy Arrays]()
43 | * [Array Operations]()
44 | * [Integration]()
45 | * [Built-In Functions]()
46 | * [Initial Value Problems]()
47 | * [Other Features]()
48 | * [Performance Comparison]()
49 | * [Projects]()
50 |
51 | You do not need to look at these notebooks before the course unless you want to.
52 |
53 | ## Post-Course Follow-Up: ReCoDE Exemplar
54 | The RCDS team has curated a collection of annotated [exemplar projects](https://imperialcollegelondon.github.io/ReCoDE-home/exemplars/) known as [ReCoDE](https://imperialcollegelondon.github.io/ReCoDE-home/) which demonstrate core research computing and data science principles applied to real problems. Each exemplar is a real project created by an Imperial student based on their research. Each exemplar is accompanied by detailed descriptions of how they work, and the design decisions taken when constructing the code. There are two Fortran exemplars:
55 |
56 | * [**Euler Maruyama Method**](https://imperialcollegelondon.github.io/ReCoDe-Euler-Maruyama/): This exemplar focuses on solving Stochastic Differential Equations in Python. The code uses Numpy arrays to store the data of the simulation and progress the simulation. It also shows how to use Numpy in an object-oriented program.
57 | * [**Using Convolutional Neural Networks to extract information from the Cosmic Dawn**](https://imperialcollegelondon.github.io/ReCoDE-FirstDawn/): This exemplar uses a Convolutional Neural Network to extract information about the early universe. Data is prepared for use in TensorFlow using Numpy arrays.
58 | * [**Binary Classification of Patent Text Using Natural Language Processing**](https://imperialcollegelondon.github.io/ReCoDE-AIForPatents/): This exemplar uses Natural Language Processing to classify patents. The code uses Numpy arrays to store and prepare the data.
59 | * [**A Multi-Channel GUI for Real-Time Data Display and Analysis**](https://imperialcollegelondon.github.io/ReCoDE-PythonGUI/): This exemplar presents a GUI for real-time data display and analysis in a medical context. The data used in the GUI is stored in Numpy arrays.
--------------------------------------------------------------------------------
/Performance Comparison.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {
7 | "id": "6qMK3uHIyKGk"
8 | },
9 | "source": [
10 | "# Performance Comparison\n",
11 | "\n",
12 | "It's difficult to produce direct performance comparison between NumPy/SciPy implementations of operations and non-NumPy/SciPy implementations because there are typically many ways of carrying out a calculation, particularly when NumPy/SciPy are not being used and particularly for more complex calculations.\n",
13 | "\n",
14 | "However, this notebook aims to make a few comparisons where possible as an illustration of how must faster NumPy and SciPy are. Where possible I have tried to use comparable/optimal implementations in non-NumPy/SciPy Python, although it's possible there are better ways."
15 | ]
16 | },
17 | {
18 | "attachments": {},
19 | "cell_type": "markdown",
20 | "metadata": {
21 | "id": "XBy0rJdCz_EF"
22 | },
23 | "source": [
24 | "## Sequence Creation\n",
25 | "\n",
26 | "We can create a few sequences (lists or NumPy arrays) and compare how long it takes for them to be created. Because the native Python implementations are using only simple commands with little actual Python code, this is comparable to the NumPy time, but will produce lists, which are not as functional as NumPy arrays."
27 | ]
28 | },
29 | {
30 | "cell_type": "code",
31 | "execution_count": null,
32 | "metadata": {
33 | "id": "7LWR_83XyF37"
34 | },
35 | "outputs": [
36 | {
37 | "name": "stdout",
38 | "output_type": "stream",
39 | "text": [
40 | "Non-NumPy zeroes: 0.06846880912780762\n",
41 | "NumPy zeroes: 0.02041029930114746\n",
42 | "Non-NumPy ascending sequence: 0.7871990203857422\n",
43 | "NumPy ascending sequence: 0.021414518356323242\n"
44 | ]
45 | }
46 | ],
47 | "source": [
48 | "import time\n",
49 | "import numpy as np\n",
50 | "\n",
51 | "# Operations will be repeated a large number of times to get a good length of time\n",
52 | "repetitions = 1000\n",
53 | "\n",
54 | "start_time = time.time()\n",
55 | "for i in range(repetitions):\n",
56 | " # Non-NumPy zeroes\n",
57 | " a = [0]*100000\n",
58 | "print('Non-NumPy zeroes:', time.time() - start_time)\n",
59 | "\n",
60 | "start_time = time.time()\n",
61 | "for i in range(repetitions):\n",
62 | " # Numpy zeroes\n",
63 | " a = np.zeros(100000)\n",
64 | "print('NumPy zeroes:', time.time() - start_time)\n",
65 | "\n",
66 | "start_time = time.time()\n",
67 | "for i in range(repetitions):\n",
68 | " # Non-NumPy ascending sequence\n",
69 | " a = list(range(100000))\n",
70 | "print('Non-NumPy ascending sequence:', time.time() - start_time)\n",
71 | "\n",
72 | "start_time = time.time()\n",
73 | "for i in range(repetitions):\n",
74 | " # NumPy ascending sequence\n",
75 | " a = np.arange(100000)\n",
76 | "print('NumPy ascending sequence:', time.time() - start_time)"
77 | ]
78 | },
79 | {
80 | "attachments": {},
81 | "cell_type": "markdown",
82 | "metadata": {
83 | "id": "OAAMEqVg5yLv"
84 | },
85 | "source": [
86 | "## Dot Product\n",
87 | "\n",
88 | "We can compare the time taken to calculate the dot product of two vectors. The NumPy version is significantly quicker and easier to read."
89 | ]
90 | },
91 | {
92 | "cell_type": "code",
93 | "execution_count": 5,
94 | "metadata": {
95 | "id": "-q458HGx2wsu"
96 | },
97 | "outputs": [
98 | {
99 | "name": "stdout",
100 | "output_type": "stream",
101 | "text": [
102 | "Non-NumPy dot product: 0.3973982334136963\n",
103 | "NumPy dot product: 0.004291534423828125\n"
104 | ]
105 | }
106 | ],
107 | "source": [
108 | "import time\n",
109 | "import numpy as np\n",
110 | "\n",
111 | "# Repeat the operations a large number of times to get a good length of time\n",
112 | "repetitions = 1000\n",
113 | " \n",
114 | "# Create the non-NumPy and NumPy arrays to be used\n",
115 | "a = list(range(10000))\n",
116 | "b = np.arange(10000)\n",
117 | "\n",
118 | "start_time = time.time()\n",
119 | "for i in range(repetitions):\n",
120 | " # Non-NumPy dot product\n",
121 | " c = sum(i[0] * i[1] for i in zip(a, a))\n",
122 | "print('Non-NumPy dot product:', time.time() - start_time)\n",
123 | "\n",
124 | "start_time = time.time()\n",
125 | "for i in range(repetitions):\n",
126 | " # Numpy dot product\n",
127 | " c = np.dot(b, b)\n",
128 | "print('NumPy dot product:', time.time() - start_time)"
129 | ]
130 | },
131 | {
132 | "attachments": {},
133 | "cell_type": "markdown",
134 | "metadata": {
135 | "id": "PSAlIc349bRy"
136 | },
137 | "source": [
138 | "## Log Function\n",
139 | "\n",
140 | "We can compare the time taken to calculate the logarithm of a single value or an array of values. When calculating a single value, the function in the ```math``` module is faster - this is because it is specifically designed to calculate the logarithm of a single value, whilst the ```numpy``` version is a more general function which works on arrays of data. As we see when we use it on the array of values later, this makes it faster when calculating the logarithm of an array of values."
141 | ]
142 | },
143 | {
144 | "cell_type": "code",
145 | "execution_count": 8,
146 | "metadata": {
147 | "id": "QZ9c620r7TDV"
148 | },
149 | "outputs": [
150 | {
151 | "name": "stdout",
152 | "output_type": "stream",
153 | "text": [
154 | "Non-NumPy single log: 0.051450252532958984\n",
155 | "NumPy single log: 0.69820237159729\n",
156 | "Non-NumPy log of list: 0.22849440574645996\n",
157 | "NumPy log of array: 0.03303098678588867\n"
158 | ]
159 | }
160 | ],
161 | "source": [
162 | "import time\n",
163 | "import math\n",
164 | "import numpy as np\n",
165 | "\n",
166 | "# Repeat the operations a large number of times to get a good length of time\n",
167 | "repetitions = 1000000\n",
168 | " \n",
169 | "# Create the non-NumPy and NumPy arrays to be used\n",
170 | "a = list(range(1, 10000))\n",
171 | "b = np.arange(1, 10000)\n",
172 | "\n",
173 | "start_time = time.time()\n",
174 | "for i in range(repetitions):\n",
175 | " # Non-NumPy single log\n",
176 | " c = math.log(2)\n",
177 | "print('Non-NumPy single log:', time.time() - start_time)\n",
178 | "\n",
179 | "start_time = time.time()\n",
180 | "for i in range(repetitions):\n",
181 | " #NumPy single log\n",
182 | " c = np.log(2)\n",
183 | "print('NumPy single log:', time.time() - start_time)\n",
184 | "\n",
185 | "# Repeat the array operations a smaller number of times\n",
186 | "repetitions = 1000\n",
187 | "\n",
188 | "start_time = time.time()\n",
189 | "for i in range(repetitions):\n",
190 | " # Non-NumPy log of list\n",
191 | " c = list(map(math.log, a))\n",
192 | "print('Non-NumPy log of list:', time.time() - start_time)\n",
193 | "\n",
194 | "start_time = time.time()\n",
195 | "for i in range(repetitions):\n",
196 | " #NumPy log of array\n",
197 | " c = np.log(b)\n",
198 | "print('NumPy log of array:', time.time() - start_time)"
199 | ]
200 | },
201 | {
202 | "attachments": {},
203 | "cell_type": "markdown",
204 | "metadata": {
205 | "id": "bVp87FkcDmeu"
206 | },
207 | "source": [
208 | "## Sinc Function\n",
209 | "\n",
210 | "The sinc function is defined as:\n",
211 | "\n",
212 | "$f(x) = \\frac{\\sin(\\pi x)}{\\pi x}$\n",
213 | "\n",
214 | "There is not a definition in the ```math``` module so we must write our own. Again, SciPy is slower for the single calculation, but much quicker for the calculation on a sequence."
215 | ]
216 | },
217 | {
218 | "cell_type": "code",
219 | "execution_count": 11,
220 | "metadata": {
221 | "id": "wzybixiy-3F0"
222 | },
223 | "outputs": [
224 | {
225 | "name": "stdout",
226 | "output_type": "stream",
227 | "text": [
228 | "Non-SciPy single sinc: 0.02415180206298828\n",
229 | "SciPy single sinc: 0.3493537902832031\n",
230 | "Non-SciPy sinc of list: 0.10430097579956055\n",
231 | "SciPy sinc of array: 0.009147405624389648\n"
232 | ]
233 | }
234 | ],
235 | "source": [
236 | "import time\n",
237 | "import math\n",
238 | "from scipy.special import sinc\n",
239 | "import numpy as np\n",
240 | "\n",
241 | "# Define a non-SciPy sinc\n",
242 | "def sinc_non_scipy(x):\n",
243 | " return math.sin(math.pi * x)/(math.pi * x)\n",
244 | "\n",
245 | "\n",
246 | "# Create the non-NumPy list and NumPy arrays to be used\n",
247 | "a = [i for i in range(1, 1000)]\n",
248 | "b = np.arange(1, 1000)\n",
249 | "\n",
250 | "# Repeat the operations a large number of times to get a good length of time\n",
251 | "repetitions = 100000\n",
252 | "\n",
253 | "start_time = time.time()\n",
254 | "for i in range(repetitions):\n",
255 | " # Non-SciPy single sinc\n",
256 | " c = sinc_non_scipy(1)\n",
257 | "print('Non-SciPy single sinc:', time.time() - start_time)\n",
258 | "\n",
259 | "start_time = time.time()\n",
260 | "for i in range(repetitions):\n",
261 | " #SciPy single sinc\n",
262 | " c = sinc(1)\n",
263 | "print('SciPy single sinc:', time.time() - start_time)\n",
264 | "\n",
265 | "# Repeat the array operations a smaller number of times\n",
266 | "repetitions = 1000\n",
267 | "\n",
268 | "start_time = time.time()\n",
269 | "for i in range(repetitions):\n",
270 | " # Non-SciPy sinc of list\n",
271 | " c = list(map(sinc_non_scipy, a))\n",
272 | "print('Non-SciPy sinc of list:', time.time() - start_time)\n",
273 | "\n",
274 | "start_time = time.time()\n",
275 | "for i in range(repetitions):\n",
276 | " #SciPy sinc of array\n",
277 | " c = sinc(b)\n",
278 | "print('SciPy sinc of array:', time.time() - start_time)"
279 | ]
280 | }
281 | ],
282 | "metadata": {
283 | "colab": {
284 | "collapsed_sections": [],
285 | "name": "Performance Comparison.ipynb",
286 | "provenance": []
287 | },
288 | "kernelspec": {
289 | "display_name": "Python 3",
290 | "language": "python",
291 | "name": "python3"
292 | },
293 | "language_info": {
294 | "codemirror_mode": {
295 | "name": "ipython",
296 | "version": 3
297 | },
298 | "file_extension": ".py",
299 | "mimetype": "text/x-python",
300 | "name": "python",
301 | "nbconvert_exporter": "python",
302 | "pygments_lexer": "ipython3",
303 | "version": "3.13.0"
304 | }
305 | },
306 | "nbformat": 4,
307 | "nbformat_minor": 0
308 | }
309 |
--------------------------------------------------------------------------------
/Initial Value Problems.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {
7 | "id": "fF91WviSH_kB"
8 | },
9 | "source": [
10 | "# Initial Value Problems\n",
11 | "\n",
12 | "Initial Value Problems are a common type of problem encountered when simulating a physical system's progression with time. Given a series of initial values, a system of Ordinary Differential Equations (ODEs) are solved to solve the progression of the system. For instance, the rate of change of the number of bacteria $N(t)$ in a growth medium might be described as:\n",
13 | "\n",
14 | "$\\frac{\\textrm{d}N(t)}{\\textrm{d}t} = \\alpha N(t)$\n",
15 | "\n",
16 | "where $\\alpha$ is a growth rate. If there are initially 10 bacteria at $t=0$, the number of bacteria as a function of time can be solved as follows:\n",
17 | "\n",
18 | "$N(t)=10e^{\\alpha t}$\n",
19 | "\n",
20 | "This example could be solved analytically, but other systems of equations are more complex. In this case, SciPy's ```scipy.integrate.solve_ivp``` function ([manual page](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html)) can be very useful for calculating the evolution of a physical system over time.\n",
21 | "\n",
22 | "```scipy.integrate.solve_ivp``` can solve the time-progression of a collection of values, presented as a vector. The function requires another function be passed to it which describes the system of equations which give the derivative as a function of the values passed to it and the current time of the system as arguments.\n",
23 | "\n",
24 | "It also takes the initial value of the system (as an array) and an array of times for which the values of the equations to be solved for are to be found. It may optionally take a tuple of values which will be passed as arguments to the function which calculates the derivative. This means the values used in the derivative function don't need to be hard-coded in the function.\n",
25 | "\n",
26 | "As an example, we'll consider the [Lorenz system of equations](https://en.wikipedia.org/wiki/Lorenz_system), which arises in simplified models of a number of physical systems including lasers, electric circuits and chemical reactions. The system of equations governing the derivative of its three variables ($x$, $y$ and $z$) are as follows:\n",
27 | "\n",
28 | "$\n",
29 | "\\frac{\\textrm{d}x(t)}{\\textrm{d}t} = \\sigma(y - x)\\\\\n",
30 | "\\frac{\\textrm{d}y(t)}{\\textrm{d}t} = x(\\rho - z) - y\\\\\n",
31 | "\\frac{\\textrm{d}z(t)}{\\textrm{d}t} = xy - \\beta z\n",
32 | "$\n",
33 | "\n",
34 | "Because we are solving for three variables as a function of time, the arrays which hold the state of the system at a particular time (or the rate of change of the system at a particular time) will be one-dimensional arrays with a size of 3.\n",
35 | "\n",
36 | "$\\beta$, $\\sigma$ and $\\rho$ are parameters which define the behaviour of the system. By picking values of 10, 8/3 and 28, we make the system chaotic, which means we expect some interesting results.\n"
37 | ]
38 | },
39 | {
40 | "cell_type": "code",
41 | "execution_count": null,
42 | "metadata": {
43 | "id": "ZOPpEATYH7AH"
44 | },
45 | "outputs": [],
46 | "source": [
47 | "import numpy as np\n",
48 | "from scipy.integrate import solve_ivp\n",
49 | "\n",
50 | "# This function returns an array described the rate of change (i.e. [dx/dt, dy/dt, dz/dt]) of the system\n",
51 | "# It accepts the current time and a vector describing the current state (i.e. [x, y, z])\n",
52 | "# The parameters sigma, beta and rho are used to accept their values from the tuple of arguments passed to solve_ivp\n",
53 | "# To be used to provide the derivative to solve_ivp, extra arguments (sigma, beta, rho) is not required by solve_ivp\n",
54 | "# However, this pattern allows \n",
55 | "def derivative(t, vec, sigma, beta, rho):\n",
56 | " # Create the array to hold the rate of change of the system\n",
57 | " result = np.zeros(3)\n",
58 | "\n",
59 | " # Calculate the rates of change\n",
60 | " result[0] = sigma * (vec[1] - vec[0])\n",
61 | " result[1] = vec[0] * (rho - vec[2]) -vec[1]\n",
62 | " result[2] = vec[0] * vec[1] - beta * vec[2]\n",
63 | "\n",
64 | " # Return the rate of change\n",
65 | " return result\n",
66 | "\n",
67 | "# Set up an array describing the start and stop time of the simulation\n",
68 | "t_span = np.array([0, 20])\n",
69 | "\n",
70 | "# Set up the array of times at which the state of the system will be calculated\n",
71 | "t_steps = np.arange(0, 20, 0.01)\n",
72 | "\n",
73 | "# Create the values representing the initial state of the system, i.e. [x(0)=1, y(0)=2, z(0)=3]\n",
74 | "initial_value = np.array([1,2,3])\n",
75 | "\n",
76 | "# Set up the parameters of the system\n",
77 | "sigma = 10\n",
78 | "beta = 8 / 3\n",
79 | "rho = 28\n",
80 | "\n",
81 | "# Calculate the time series of values\n",
82 | "# The returned value contains the state of the system at each of the requested times\n",
83 | "# The first dimension of time_series represents the different times\n",
84 | "# The second dimension represents the different values of the system, e.g. [x, y, z]\n",
85 | "# For instance time_series[10, 1] represents the value of y at the 11th time\n",
86 | "results = solve_ivp(derivative, t_span, initial_value, t_eval=t_steps, args = (sigma, beta, rho))\n",
87 | "\n",
88 | "# The results returned is an object which contains the results of the simulation and associated messages and values\n",
89 | "print(type(results))\n",
90 | "print(results)\n",
91 | "# The most important data is the time of the time steps of the outputs stored in results.t and the values of the variables as a function of time in results.y\n",
92 | "\n",
93 | "# Print the output shape. The first dimension relates to each of the output variables we're solving for. The second relates to the time steps\n",
94 | "print(\"Output shape: \", results.y.shape)\n",
95 | "#Print the times array (numpy skips the middle values for a large array)\n",
96 | "print(\"Time: \", results.t)\n",
97 | "# Print the first values of the system\n",
98 | "print(\"First: \", results.y[:, 0])\n",
99 | "#Print the last values of the system\n",
100 | "print(\"Last: \", results.y[:, -1])"
101 | ]
102 | },
103 | {
104 | "cell_type": "markdown",
105 | "metadata": {},
106 | "source": [
107 | "The cell below plots the results. Make sure to run the cell above to create `results` before running the cell below."
108 | ]
109 | },
110 | {
111 | "cell_type": "code",
112 | "execution_count": null,
113 | "metadata": {},
114 | "outputs": [],
115 | "source": [
116 | "import matplotlib.pyplot as plt\n",
117 | "\n",
118 | "# If you're unfamiliar with matplotlib, you can ignore the details\n",
119 | "fig = plt.figure()\n",
120 | "plt.subplots_adjust(hspace=0.5)\n",
121 | "fig = plt.figure(figsize=plt.figaspect(4))\n",
122 | "\n",
123 | "ax = fig.add_subplot(4, 1, 1)\n",
124 | "ax.plot(results.t, results.y[0, :])\n",
125 | "ax.set_title('x(t)')\n",
126 | "\n",
127 | "ax = fig.add_subplot(4, 1, 2)\n",
128 | "ax.plot(results.t, results.y[1, :])\n",
129 | "ax.set_title('y(t)')\n",
130 | "\n",
131 | "ax = fig.add_subplot(4, 1, 3)\n",
132 | "ax.plot(results.t, results.y[2, :])\n",
133 | "ax.set_title('z(t)')\n",
134 | "\n",
135 | "ax = fig.add_subplot(4, 1, 4, projection='3d')\n",
136 | "ax.plot(results.y[0, :], results.y[1, :], results.y[2, :])\n",
137 | "\n",
138 | "plt.show()"
139 | ]
140 | },
141 | {
142 | "attachments": {},
143 | "cell_type": "markdown",
144 | "metadata": {
145 | "id": "EDmQfguYWI3L"
146 | },
147 | "source": [
148 | "## Exercise\n",
149 | "\n",
150 | "A predator-prey cycle describes a dynamic system where the number of members of a prey species and a predator species are linked. This relationship means that when the population of prey is low, the population of predators falls, and when it is high the number of predators falls. When the number of predators is high the number of prey falls, and when the number of predators is low the number of prey rises. This type of equation is described by the [Lotka-Volterra equations](https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations):\n",
151 | "\n",
152 | "$\n",
153 | "\\frac{\\textrm{d}x(t)}{\\textrm{d}t} = \\alpha x - \\beta xy\\\\\n",
154 | "\\frac{\\textrm{d}y(t)}{\\textrm{d}t} = \\delta xy - \\gamma y\\\\\n",
155 | "$\n",
156 | "\n",
157 | "where $x$ is the number of prey, $y$ is the number of predators and $\\alpha$, $\\beta$, $\\gamma$ and $\\delta$ are parameters which describe the behaviour of the system.\n",
158 | "\n",
159 | "In the code below, use ```solve_ivp``` to calculate the time progression over 100 months of the number of prey and predators in a system given that there are initially 1000 predators and 2000 prey and the system has the following values:\n",
160 | "\n",
161 | "$\\alpha = 1/\\textrm{month}\\\\\n",
162 | "\\beta = 5\\times10^{-4}/\\textrm{month}\\\\\n",
163 | "\\gamma = 0.5/\\textrm{month}\\\\\n",
164 | "\\delta = 1\\times10^{-4}/\\textrm{month}$\n",
165 | "\n",
166 | "The code already provided will plot the output, providing the results of ```solve_ivp``` in the variable ```results```."
167 | ]
168 | },
169 | {
170 | "cell_type": "code",
171 | "execution_count": null,
172 | "metadata": {
173 | "id": "v6cT0tMTT9OJ"
174 | },
175 | "outputs": [],
176 | "source": [
177 | "\n",
178 | "\n",
179 | "\n",
180 | "\n",
181 | "# Below here are the commands to plot the output figure\n",
182 | "# You don't need to and shouldn't edit them\n",
183 | "import matplotlib.pyplot as plt\n",
184 | "plt.plot(results.t, results.y[0, :], label = \"Prey\")\n",
185 | "plt.plot(results.t, results.y[1, :], label = \"Predator\")\n",
186 | "plt.legend()"
187 | ]
188 | },
189 | {
190 | "cell_type": "code",
191 | "execution_count": null,
192 | "metadata": {
193 | "cellView": "form",
194 | "id": "OF2x78IbfvN8"
195 | },
196 | "outputs": [],
197 | "source": [
198 | "#@title\n",
199 | "\n",
200 | "import numpy as np\n",
201 | "from scipy.integrate import solve_ivp\n",
202 | "\n",
203 | "# The function which defines the rate of change with the system as a function of time\n",
204 | "def derivative(t, vec, alpha, beta, gamma, delta):\n",
205 | " # Create the array to hold the rate of change of the system\n",
206 | " result = np.zeros(2)\n",
207 | "\n",
208 | " # Calculate the rates of change\n",
209 | " result[0] = alpha * vec[0] - beta * vec[0] * vec[1]\n",
210 | " result[1] = delta * vec[0] * vec[1] - gamma * vec[1]\n",
211 | "\n",
212 | " # Return the rate of change\n",
213 | " return result\n",
214 | "\n",
215 | "# Set up an array describing the start and stop time of the simulation\n",
216 | "t_span = np.array([0, 100])\n",
217 | "\n",
218 | "# Set up the array of times at which the state of the system will be calculated\n",
219 | "t_steps = np.arange(0, 100, 0.01)\n",
220 | "\n",
221 | "# Create the values representing the initial state of the system, i.e. 2000 prey and 1000 predators\n",
222 | "initial_value = np.array([2000, 1000])\n",
223 | "\n",
224 | "# Set up the parameters of the system\n",
225 | "alpha = 1\n",
226 | "beta = 5e-4\n",
227 | "gamma = 0.5\n",
228 | "delta = 1e-4\n",
229 | "\n",
230 | "# Solve the initial value problem\n",
231 | "results = solve_ivp(derivative, t_span, initial_value, t_eval=t_steps, args = (alpha, beta, gamma, delta))\n",
232 | "\n",
233 | "# Below here are the commands to plot the output figure\n",
234 | "# You don't need to and shouldn't edit them\n",
235 | "import matplotlib.pyplot as plt\n",
236 | "plt.plot(results.t, results.y[0, :], label = \"Prey\")\n",
237 | "plt.plot(results.t, results.y[1, :], label = \"Predator\")\n",
238 | "plt.legend()"
239 | ]
240 | }
241 | ],
242 | "metadata": {
243 | "colab": {
244 | "collapsed_sections": [],
245 | "name": "Initial Value Problems.ipynb",
246 | "provenance": []
247 | },
248 | "kernelspec": {
249 | "display_name": "Python 3",
250 | "language": "python",
251 | "name": "python3"
252 | },
253 | "language_info": {
254 | "codemirror_mode": {
255 | "name": "ipython",
256 | "version": 3
257 | },
258 | "file_extension": ".py",
259 | "mimetype": "text/x-python",
260 | "name": "python",
261 | "nbconvert_exporter": "python",
262 | "pygments_lexer": "ipython3",
263 | "version": "3.13.2"
264 | },
265 | "vscode": {
266 | "interpreter": {
267 | "hash": "5dbf766eb95023f9dddb799b5381b4ed6a9322e38632e8ac1570872c767b304b"
268 | }
269 | }
270 | },
271 | "nbformat": 4,
272 | "nbformat_minor": 0
273 | }
274 |
--------------------------------------------------------------------------------
/Creating and Manipulating NumPy Arrays.ipynb:
--------------------------------------------------------------------------------
1 | {"cells":[{"cell_type":"markdown","metadata":{"id":"vNewmPBj-V1j"},"source":["# Creating and Manipulating NumPy Arrays\n","\n","## Arrays\n","\n","One of the most important constructs in NumPy is the ```array```. To create an array, we first need to import the NumPy module into our code. When we do this, it is common to give it the alias ```np```. We can then create an array by writing ```np.array()``` and providing a sequence (such as a list or tuple) of values."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"wOEdh9Mk-Pr3"},"outputs":[],"source":["import numpy as np\n","\n","my_array = np.array([1, 2, 3])"]},{"cell_type":"markdown","metadata":{"id":"NhXecD0kJnw_"},"source":["## Printing Arrays\n","\n","We can print arrays as normal using the print statement:"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"H4KRo3P5KAPh"},"outputs":[],"source":["print(my_array)\n","\n","print([1, 2, 3])"]},{"cell_type":"markdown","metadata":{"id":"9DOpwYwWxsgz"},"source":["Note that, when we print an array, it is printed within square brackets and without commas separating values, whilst a list is printed with commas. This allows us to easily distinguish these data types when they're printed."]},{"cell_type":"markdown","metadata":{"id":"goT3VSnFKDii"},"source":["### Array Data Types\n","\n","The elements of an array must all the same type. So we can create an array of strings or ```bool``s:"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"TXD82TxLxrXm"},"outputs":[],"source":["import numpy as np\n","\n","string_array = np.array([\"str1\", \"str2\"])\n","print(string_array)\n","\n","bool_array = np.array([True, False])\n","print(bool_array)"]},{"cell_type":"markdown","metadata":{"id":"XHYdZEOnyc1N"},"source":["However, if we try to define an array with a mixture of types, NumPy will try to convert some or all of the values so that all values have the same type."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"xfQwR2gbyayA"},"outputs":[],"source":["import numpy as np\n","\n","mixed_array = np.array([1, 1.2])\n","print(mixed_array) # Both values are converted to floats\n","\n","mixed_array2 = np.array([4, \"str\"])\n","print(mixed_array2) # Both values are converted to strings"]},{"cell_type":"markdown","metadata":{"id":"i9isTUhNzu1L"},"source":["We can check the type of data stored in an array using the ```dtype``` property:"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"gbiuKbuYyl_A"},"outputs":[],"source":["import numpy as np\n","\n","array1 = np.array([1, 2])\n","print(array1.dtype)\n","\n","array2 = np.array([True, False])\n","print(array2.dtype)\n","\n","array3 = np.array([\"1\", \"2\"])\n","print(array3.dtype)\n","\n","array4 = np.array([\"1234567890\", \"2\"])\n","print(array4.dtype)\n","\n","array5 = np.array([1.0, -2.0])\n","print(array5.dtype)"]},{"attachments":{},"cell_type":"markdown","metadata":{"id":"T6eRHjI70qo4"},"source":["Note that the data types reported are different to normal Python data types - these are the types in the underlying C code which NumPy uses to store the data. For instance, ```int64``` describes as 64-bit integer, ```= 0:\n"," # Calculate the rate of change of x and y\n"," result[0] = vec[2]\n"," result[1] = vec[3]\n","\n"," #Calculate the speed of the projectile as we'll use this multiple times\n"," speed = math.sqrt(vec[2] ** 2 + vec[3] ** 2)\n","\n"," # Calculate the rate of change of the speed of the projectile\n"," result[2] = -alpha * vec[2] * speed / mass\n"," result[3] = -alpha * vec[3] * speed / mass -9.81 \n","\n"," # Return the derivative\n"," return result\n","\n","# Calculate the initial value of the state of the system\n","initial_value = np.array([0, 0, 100 * math.cos(math.pi / 4), 100 * math.sin(math.pi / 4)])\n","\n","# Set the values of alpha and mass\n","alpha = 0.1\n","mass = 20\n","\n","#Create the start and end time\n","t_span = np.array([0, 10])\n","\n","# Create the times at which we want to know [x,y,u,v]\n","t_steps=np.arange(0, 10, 0.1)\n","\n","# Find the values of [x,y,u,v] as a function of time\n","results = solve_ivp(derivative, t_span, initial_value, t_eval=t_steps, args = (alpha, mass))\n","\n","# Print the time series to get an accurate measure of y(x)\n","# We find y=0 when x=258m so this is when the cannonball hits the ground\n","print(results.y)\n","\n","# Below here are the commands to plot the output figure\n","# You don't need to and shouldn't edit them\n","# Expects results to be the returned value from solve_ivp\n","import matplotlib.pyplot as plt\n","plt.plot(results.y[0, :], results.y[1, :])"]},{"attachments":{},"cell_type":"markdown","metadata":{"id":"_d2BQpNLYv7L"},"source":["## Thermal Diffusion\n","\n","Skills:\n","* Array operations\n","* Initial Value Problems\n","\n","Difficulty: Hard\n","\n","The one-dimensional time-dependent thermal diffusion equation (or heat equation) may be written as follows:\n","\n","$\\frac{\\partial T(x,t)}{\\partial t} = \\alpha \\frac{\\partial ^{2} T(x,t)}{\\partial x^{2}} + H(x,t)$\n","\n","where $T(t,x)$ is the temperature of the region being solved for, $t$ is time, $x$ is the distance coordinate, $\\alpha$ is a constant relating to thermal diffusion within the rod, and $H(x,t)$ is a function which describes the rate of change of temperature due to heating as a function of time and location..\n","\n","We want to find the temperature distribution as a function of time along a metal rod with a length of 1m. Both ends (at $x$=0m and $x$=1m) are held at a constant temperature of 300K. The rod has an initial temperature of 300K at all values of $x$ and is heated such that $H(x,t)$=1K/s and the physical properties of the rod are such that $\\alpha$=Km$^{2}$/s.\n","\n","We want to know the temperature every centimeter (e.g. $x$=0m, 0.01m, 0.02m ... 0.99m, 1m) along the rod. We can phrase this as an initial value problem, where we describe the state of the system as $\\vec{T}(t)$ where the first element of $\\vec{T}(t)$ is the temperature at $x=0$m, the next is the temperature at $x=0.01$m and so on until the last (101st) element represents the temperature at $x=1$m.\n","\n","We can then represent the rate of change of the system as:\n","\n","$\\frac{\\textrm{d}\\vec{T}(t)}{\\textrm{d}t} = M\\vec{T}(t) + \\vec{H}$\n","\n","where $M$ is a matrix and $\\vec{H}$ is a vector with one entry per position the temperature is being calculated at (i.e. with 101 entries). The term $M\\vec{T}(t)$ represents the change of temperature due to thermal conduction along the rod and $\\vec{H}$ represents the change of temperature due to heating of the rod.\n","\n","$M$ is defined as follows:\n","\n","$M = \\begin{pmatrix}\n","0 & 0 & 0 & 0 & 0 & \\dots & 0 & 0 & 0 & 0 & 0\\\\ \n","0.5 & -1 & 0.5 & 0 & 0 & \\dots & 0 & 0 & 0 & 0 & 0\\\\ \n","0 & 0.5 & -1 & 0.5 & 0 & \\dots & 0 & 0 & 0 & 0 & 0\\\\ \n","0 & 0 & 0.5 & -1 & 0.5 & \\dots & 0 & 0 & 0 & 0 & 0\\\\ \n"," & & & & & \\ddots & & & & & \\\\ \n","0 & 0 & 0 & 0 & 0 & \\dots & 0.5 & -1 & 0.5 & 0 & 0\\\\ \n","0 & 0 & 0 & 0 & 0 & \\dots & 0 & 0.5 & -1 & 0.5 & 0\\\\ \n","0 & 0 & 0 & 0 & 0 & \\dots & 0 & 0 & 0.5 & -1 & 0.5 \\\\\n","0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \n","\\end{pmatrix}$\n","\n","i.e. The first and last row are all zero. Otherwise, the lead diagonal is all -1 and the adjacent diagonals are all 0.5.\n","\n","$H$ is defined such that its first and last values are 0 but it is otherwise 0.1:\n","\n","$H = \\begin{pmatrix}\n","0 \\\\\n","0.1 \\\\\n","0.1 \\\\\n","\\vdots \\\\\n","0.1 \\\\\n","0.1 \\\\\n","0\n","\\end{pmatrix}$\n","\n","Write a piece of code in the cell below which creates the matrix $M$ and vector $H$ and passes these to ```scipy.integrate.odeint``` as arguments. Define the rest of the initial value problem to solve the differential equation for the values of $\\vec{T}(t)$ every second up to $t=1000s$. Your derivative function should use matrix operations.\n","\n","Code to plot the results as a function of time has already been provided. To make sure the results are plotted correctly, take care that your results contain the temperatures every 1s.\n","\n","Extension: You may see some small oscillations in your result. These are due to numerical instability. Consult the [documentation](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.solve_ivp.html) for ```solve_ivp``` and find out how to reduce the maximum step size of the solver to 0.1s. This is distinct from the times at which the results are output defined by ```t_eval``` and forces the solver to take shorter steps, reducing the numerical error. You should see the results no longer have the oscillations. The reasons for this oscillation are linked to the [Courant–Friedrichs–Lewy condition](https://en.wikipedia.org/wiki/Courant%E2%80%93Friedrichs%E2%80%93Lewy_condition)."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"IH4xMgu7eNZa"},"outputs":[],"source":["\n","\n","\n","\n","\n","\n","\n","\n","\n","# Below here are the commands to plot the output figure\n","# You don't need to and shouldn't edit them\n","# Expects the results of solve_ivp to be in the variable \"results\"\n","import numpy as np\n","import matplotlib.pyplot as plt\n","x = np.arange(0, 1.001, 0.01)\n","for i in range(0, 1001, 100):\n"," plt.plot(x, results.y[:, i], label = \"t = \"+str(i)+\"s\")\n","\n","plt.legend()"]},{"cell_type":"code","execution_count":null,"metadata":{"cellView":"form","id":"mgbAoYUYXeeU"},"outputs":[],"source":["#@title\n","\n","import numpy as np\n","from scipy.integrate import solve_ivp\n","import matplotlib.pyplot as plt\n","\n","# Define the derivative function\n","# It takes conduction matrix and heating vector as arguments\n","def derivative(t, temperature, conduction_matrix, heating):\n"," # Calculate MT + H using matmul for the matrix multiplication\n"," # Return the calculated value\n"," return np.matmul(conduction_matrix, temperature) + heating\n","\n","\n","# Calculate the conduction matrix\n","# This matrix has lots of zeroes, so you could also chose to use the sparse matrix as introduced in the extension of the Array Operations notebook\n","conduction_matrix = np.zeros(101 * 101).reshape([101, 101])\n","for i in range(1,100):\n"," conduction_matrix[i,i] = -1 # The lead diagonal\n"," conduction_matrix[i, i - 1] = 0.5 # The diagonal to the left of the lead diagonal\n"," conduction_matrix[i, i + 1] = 0.5 # The diagonal to the right of the lead diagonal\n","\n","# Create the heating vector\n","heating = np.zeros(101)\n","heating[1:100] = 0.1\n","\n","# Calculate the initial value of the state of the system\n","initial_value = np.full(101, 300)\n","\n","# Create the bounds of the time the problem is solved for\n","t_span = np.array([0, 1000])\n","\n","# Create the array of output times\n","t_steps = np.arange(0, 1000.01, 1)\n","\n","# Calculate the temperature profile as a function of time\n","results = solve_ivp(derivative, t_span, initial_value, t_eval=t_steps, args=(conduction_matrix, heating))\n","# This is the version of the call with the limited step size\n","#results = solve_ivp(derivative, t_span, initial_value, t_eval=t_steps, args=(conduction_matrix, heating), max_step=0.1)\n","\n","# Below here are the commands to plot the output figure\n","# You don't need to and shouldn't edit them\n","# Expects the results of solve_ivp to be in the variable \"results\"\n","import numpy as np\n","import matplotlib.pyplot as plt\n","x = np.arange(0, 1.001, 0.01)\n","for i in range(0, 1001, 100):\n"," plt.plot(x, results.y[:, i], label = \"t = \"+str(i)+\"s\")\n","\n","plt.legend()"]}],"metadata":{"colab":{"collapsed_sections":[],"name":"Projects.ipynb","provenance":[]},"kernelspec":{"display_name":"Python 3","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.9.13"},"vscode":{"interpreter":{"hash":"5dbf766eb95023f9dddb799b5381b4ed6a9322e38632e8ac1570872c767b304b"}}},"nbformat":4,"nbformat_minor":0}
2 |
--------------------------------------------------------------------------------
/Array Operations.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "id": "euQr57ooLW_e"
7 | },
8 | "source": [
9 | "# Array Operations\n",
10 | "\n",
11 | "NumPy is not just good at storing large amounts of data, it's also very efficient at performing calculations and makes carrying out these calculations very convenient. This notebook discusses some of these calculations and the syntax used to invoke them."
12 | ]
13 | },
14 | {
15 | "cell_type": "markdown",
16 | "metadata": {
17 | "id": "u5i0HFbT8wPP"
18 | },
19 | "source": [
20 | "## Arithmetic Operations\n",
21 | "\n",
22 | "Simple arithmetic operators can be performed using NumPy arrays. When such an operation is placed between two NumPy arrays, the operation is performed \"element-wise\". This means that the operation is performed on each element of the arrays to create the corresponding element of the new array. For instance:"
23 | ]
24 | },
25 | {
26 | "cell_type": "code",
27 | "execution_count": null,
28 | "metadata": {
29 | "id": "JCRxTeXwLS4k"
30 | },
31 | "outputs": [],
32 | "source": [
33 | "import numpy as np\n",
34 | "\n",
35 | "a = np.array([2,4,6])\n",
36 | "b = np.array([1,4,7])\n",
37 | "\n",
38 | "print(\"Addition: \", a + b)\n",
39 | "print(\"Subtraction: \", a - b)\n",
40 | "print(\"Multiplication: \", a * b)\n",
41 | "print(\"Division: \", a / b)\n",
42 | "print(\"Exponentiation: \", a ** b)\n",
43 | "print(\"Integer Division: \", a // b)\n",
44 | "print(\"Modulo: \", a % b)\n",
45 | "print(\"Negative: \", -a)\n",
46 | "\n",
47 | "# This is also works with comparison operators\n",
48 | "print(\"Greater Than: \", a > b)\n",
49 | "print(\"Less Than: \", a < b)\n",
50 | "print(\"Greater Than or Equal: \", a >= b)\n",
51 | "print(\"Less Than or Equal: \", a <= b)\n",
52 | "print(\"Equal: \", a == b)\n",
53 | "print(\"Not Equal: \", a != b)"
54 | ]
55 | },
56 | {
57 | "cell_type": "markdown",
58 | "metadata": {
59 | "id": "CsDS8CwtC-mw"
60 | },
61 | "source": [
62 | "Note that, when operating on arrays in this way, both arrays must be the same dimension and size."
63 | ]
64 | },
65 | {
66 | "cell_type": "code",
67 | "execution_count": null,
68 | "metadata": {
69 | "id": "YkAyNiKTBy62"
70 | },
71 | "outputs": [],
72 | "source": [
73 | "import numpy as np\n",
74 | "\n",
75 | "# These arrays have the same number of elements but a different number of dimensions\n",
76 | "a = np.arange(4).reshape([2,2])\n",
77 | "b = np.arange(1,5)\n",
78 | "\n",
79 | "print(a + b)"
80 | ]
81 | },
82 | {
83 | "cell_type": "markdown",
84 | "metadata": {
85 | "id": "nHMa3tm6DK-K"
86 | },
87 | "source": [
88 | "## Applying the Same Operation to Every Element\n",
89 | "\n",
90 | "It's possible to apply the same operation to every element of an array using an operator and a scalar value. The array and the integer may be in either order."
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "execution_count": null,
96 | "metadata": {
97 | "id": "88r4jziiC78q"
98 | },
99 | "outputs": [],
100 | "source": [
101 | "import numpy as np\n",
102 | "\n",
103 | "a = np.arange(1, 4)\n",
104 | "print(\"a: \", a)\n",
105 | "print(\"Addition: \", 2 + a)\n",
106 | "print(\"Left Subtraction: \", 2 - a)\n",
107 | "print(\"Right Subtraction: \", a - 2)\n",
108 | "print(\"Multiplication: \", a * 2)\n",
109 | "print(\"Left Division: \", a / 2)\n",
110 | "print(\"Right Division: \", 2 / a)\n",
111 | "print(\"Left Exponentiation: \", 2 ** a)\n",
112 | "print(\"Right Exponentiation: \", a ** 2)\n",
113 | "print(\"Left Integer Division: \", 2 // a)\n",
114 | "print(\"Right Integer Division: \", a // 2)\n",
115 | "print(\"Left Modulo: \", 2 % a)\n",
116 | "print(\"Right Modulo: \", a % 2)\n",
117 | "print(\"Left Greater Than: \", 2 > a)\n",
118 | "print(\"Right Greater Than: \", a > 2)"
119 | ]
120 | },
121 | {
122 | "cell_type": "markdown",
123 | "metadata": {
124 | "id": "TdepIcpKuhlC"
125 | },
126 | "source": [
127 | "## Exercise\n",
128 | "\n",
129 | "Look at the code in the cell below, but don't run it yet. Instead, write down what you think will be printed in each case. Then, run the code and check you get the results you expect."
130 | ]
131 | },
132 | {
133 | "cell_type": "code",
134 | "execution_count": null,
135 | "metadata": {
136 | "id": "cPW87SEVu2xR"
137 | },
138 | "outputs": [],
139 | "source": [
140 | "import numpy as np\n",
141 | "\n",
142 | "a = np.array([1, 2])\n",
143 | "b = np.array([3, 4])\n",
144 | "\n",
145 | "print(\"Case 1: \", a + b)\n",
146 | "print(\"Case 2: \", a / 2)\n",
147 | "print(\"Case 3: \", a ** b)\n",
148 | "print(\"Case 4: \", 2 - a)\n",
149 | "print(\"Case 5: \", a - b * 2) # Remember the order of operations"
150 | ]
151 | },
152 | {
153 | "cell_type": "markdown",
154 | "metadata": {},
155 | "source": [
156 | "## Statistics of Arrays\n",
157 | "NumPy has a number of built-in functions for performing statistical calculations on arrays. These include:"
158 | ]
159 | },
160 | {
161 | "cell_type": "code",
162 | "execution_count": null,
163 | "metadata": {},
164 | "outputs": [],
165 | "source": [
166 | "a = np.array([1, 4, 3, 5, 2])\n",
167 | "\n",
168 | "print(\"a: \", a)\n",
169 | "print(\"Sum: \", a.sum())\n",
170 | "print(\"Minimum: \", a.min())\n",
171 | "print(\"Maximum: \", a.max())\n",
172 | "print(\"Mean: \", a.mean())\n",
173 | "print(\"Median: \", np.median(a))\n",
174 | "print(\"Standard Deviation: \", a.std())\n",
175 | "print(\"Variance: \", a.var())\n",
176 | "print(\"Range:\", np.ptp(a))"
177 | ]
178 | },
179 | {
180 | "cell_type": "markdown",
181 | "metadata": {},
182 | "source": [
183 | "We can also perform useful operations on an array of ```bool```s (such as those generated by a comparison operator):"
184 | ]
185 | },
186 | {
187 | "cell_type": "code",
188 | "execution_count": null,
189 | "metadata": {},
190 | "outputs": [],
191 | "source": [
192 | "a = np.array([True, True, False, True])\n",
193 | "\n",
194 | "print(\"a: \", a)\n",
195 | "print(\"All: \", a.all()) # Will be True if all elements are True\n",
196 | "print(\"Any: \", a.any()) # Will be True if any elements are True\n",
197 | "print(\"Number of Trues: \", a.sum()) # For the purposes of a sum, True is 1 and False is 0\n",
198 | "\n",
199 | "# We can combine these functions with operators\n",
200 | "# For example, to check if all elements are less than 10:\n",
201 | "b = np.array(np.array([1, 2, 3, 4]))\n",
202 | "print(\"All less than 10: \", (b < 10).all())"
203 | ]
204 | },
205 | {
206 | "cell_type": "markdown",
207 | "metadata": {
208 | "id": "dI0EufaRG5vq"
209 | },
210 | "source": [
211 | "## More Complex Functions\n",
212 | "\n",
213 | "Many complex mathematical functions which operate on scalars in Python are available from the ```math``` module, such as the ```sin``` function. These functions will work on NumPy arrays with a size of 1 (and return a scalar), but will not work on larger arrays:"
214 | ]
215 | },
216 | {
217 | "cell_type": "code",
218 | "execution_count": null,
219 | "metadata": {
220 | "id": "ujJ6d7hnI2Hb"
221 | },
222 | "outputs": [],
223 | "source": [
224 | "import numpy as np\n",
225 | "import math\n",
226 | "\n",
227 | "print(math.sin(2))\n",
228 | "print(math.sin(np.array([1])))\n",
229 | "print(math.sin(np.arange(3)))"
230 | ]
231 | },
232 | {
233 | "cell_type": "markdown",
234 | "metadata": {
235 | "id": "a9i2LXv0JW-i"
236 | },
237 | "source": [
238 | "Fortunately, many of these functions are replicated within the NumPy array and will operate element-wise on an array passed to it:"
239 | ]
240 | },
241 | {
242 | "cell_type": "code",
243 | "execution_count": null,
244 | "metadata": {
245 | "id": "UwkG-8HLDcAK"
246 | },
247 | "outputs": [],
248 | "source": [
249 | "import numpy as np\n",
250 | "\n",
251 | "# Here we multiply the array [1 2 3] by a quarter\n",
252 | "a = np.arange(1,4) * 0.25\n",
253 | "print(\"a: \", a)\n",
254 | "print(\"sin: \", np.sin(a))\n",
255 | "print(\"arccos: \", np.arccos(a))\n",
256 | "print(\"log: \", np.log(a))\n",
257 | "print(\"log2: \", np.log2(a))\n",
258 | "print(\"log10: \", np.log10(a))\n",
259 | "print(\"sqrt: \", np.sqrt(a))"
260 | ]
261 | },
262 | {
263 | "cell_type": "markdown",
264 | "metadata": {},
265 | "source": [
266 | "## Exercise: Heat Pump Energy Usage\n",
267 | "\n",
268 | "Domestic heat pumps use electricity to transfer heat from one place to another. In a domestic settings they are usually used to pump heat from the cold outdoors to the warmer indoors. As they capture some of the heat from the outside air, they can be more efficient than traditional electric heating. \n",
269 | "\n",
270 | "We're going to do some calculations that will help us understand the power usage of a heat pump throughout a typical day. We will do this by calculating a variety of properties throughout the day. As a result, many values will be stored in a 1-D Numpy array with 24 entries. The first entry in the array will relate to around midnight, the next will relate around 1am, and so on.\n",
271 | "\n",
272 | "On a particular day, the external temperature is defined as follows:\n",
273 | "\n",
274 | "$$\n",
275 | "T_{out} = 270 + 10 \\sin\\left(\\frac{\\pi}{12}(t - 6)\\right)\n",
276 | "$$\n",
277 | "\n",
278 | "where:\n",
279 | "\n",
280 | "- $T_{out}$ is the outdoor temperature (K)\n",
281 | "- $t$ is the time in hours (0 for the first hour, 23 for the last hour)\n",
282 | "\n",
283 | "$T_{out}$ is measured in units of [Kelvin](https://en.wikipedia.org/wiki/Kelvin).\n",
284 | "\n",
285 | "The indoor temperature $T_{in}$ is the temperature that the owners want to maintain. This is a constant value of 293K.\n",
286 | "\n",
287 | "In the cell below, complete the code to calculate the indoor and outdoor temperatures and represent them in Numpy arrays. You may include other lines of code before the existing lines if you like. Sample solutions to each step of this exercise are found in a code cell at the end of it."
288 | ]
289 | },
290 | {
291 | "cell_type": "code",
292 | "execution_count": null,
293 | "metadata": {},
294 | "outputs": [],
295 | "source": [
296 | "# Exterior temperature (K)\n",
297 | "t_out = \n",
298 | "\n",
299 | "# Interior temperature (K)\n",
300 | "t_in = \n",
301 | "\n",
302 | "print(\"Outdoor Temperature (K):\", t_out) # Should be 24 values. First should be 260\n",
303 | "print(\"Indoor Temperature (K):\", t_in) # Should be 24 values, all 293"
304 | ]
305 | },
306 | {
307 | "cell_type": "markdown",
308 | "metadata": {},
309 | "source": [
310 | "If we make some approximations, the equation to calculate the heating power from a heat pump is:\n",
311 | "\n",
312 | "$$\n",
313 | "P_{heat} = \\frac{P_{elec}}{\\left(1-\\frac{T_{out}}{T_{in}}\\right)},\n",
314 | "$$\n",
315 | "\n",
316 | "where:\n",
317 | " - $P_{heat}$ is the heating power (W)\n",
318 | " - $P_{elec}$ is the electrical power input (W)\n",
319 | "\n",
320 | "We can approximate the heat loss from a house as being equal to:\n",
321 | "\n",
322 | "$$\n",
323 | "P_{loss} = UA(T_{in} - T_{out}),\n",
324 | "$$\n",
325 | "\n",
326 | "where:\n",
327 | "- $P_{loss}$ is the heat loss (W)\n",
328 | "- $U$ is the overall heat transfer coefficient (W/m²K)\n",
329 | "- $A$ is the surface area of the house (m²)\n",
330 | "\n",
331 | "In winter, to maintain a constant temperature, the heat pump will supply a heating power which equals the heat loss, such that:\n",
332 | "\n",
333 | "$$\n",
334 | "P_{heat} = P_{loss}\n",
335 | "$$\n",
336 | "\n",
337 | "Substituting the expressions for $P_{heat}$ and $P_{loss}$ and rearranging, we find:\n",
338 | "\n",
339 | "$$\n",
340 | "P_{elec} = UA(T_{in} - T_{out})\\left(1-\\frac{T_{out}}{T_{in}}\\right)\n",
341 | "$$\n",
342 | "\n",
343 | "In the cell below the values of $U$ and $A$ have already been defined. Write a Numpy expression to calculate the electrical power usage for each hour of the day. You should use `t_in` and `t_out` from the code cell above in your calculation."
344 | ]
345 | },
346 | {
347 | "cell_type": "code",
348 | "execution_count": null,
349 | "metadata": {},
350 | "outputs": [],
351 | "source": [
352 | "U = 2 # Heat transfer coefficient (W/m²K)\n",
353 | "A = 100 # Heat transfer surface area (m²)\n",
354 | "\n",
355 | "# Electrical power (W)\n",
356 | "p_elec =\n",
357 | "\n",
358 | "print(\"Electrical Power (W) each hour:\", p_elec) # Should be 24 values. First should be 743.3-ish"
359 | ]
360 | },
361 | {
362 | "cell_type": "markdown",
363 | "metadata": {},
364 | "source": [
365 | "To calculate the energy used each hour in units of J, we need to multiply the power usage in each hour by the number of seconds in an hour (3600):\n",
366 | "\n",
367 | "$$\n",
368 | "E_{elec} = 3600 P_{elec}\n",
369 | "$$\n",
370 | "\n",
371 | "where:\n",
372 | " - $E_{elec}$ is the electrical energy used (J)\n",
373 | "\n",
374 | "In the cell below, calculate the energy used in each hour:"
375 | ]
376 | },
377 | {
378 | "cell_type": "code",
379 | "execution_count": null,
380 | "metadata": {},
381 | "outputs": [],
382 | "source": [
383 | "# Energy used in each of the 24 1-hour periods (J)\n",
384 | "e_elec =\n",
385 | "\n",
386 | "print(\"Electrical Energy (J) each hour:\", e_elec) # Should be 24 values. First should be 2676040-ish"
387 | ]
388 | },
389 | {
390 | "cell_type": "markdown",
391 | "metadata": {},
392 | "source": [
393 | "The cost of electricity is 25.73p/kWh. If we convert this to a cost per J, the cost is 0.0000000714 £/J. In the cell below, calculate the total cost of electricity used over the 24-hour period:"
394 | ]
395 | },
396 | {
397 | "cell_type": "code",
398 | "execution_count": null,
399 | "metadata": {},
400 | "outputs": [],
401 | "source": [
402 | "# Total 24-hour cost (£)\n",
403 | "total_cost = \n",
404 | "\n",
405 | "print(\"Total 24-hour cost (£):\", total_cost) # Should be a single value of 2.44-ish"
406 | ]
407 | },
408 | {
409 | "cell_type": "markdown",
410 | "metadata": {},
411 | "source": [
412 | "### Sample Solution"
413 | ]
414 | },
415 | {
416 | "cell_type": "code",
417 | "execution_count": null,
418 | "metadata": {},
419 | "outputs": [],
420 | "source": [
421 | "#@title\n",
422 | "\n",
423 | "import numpy as np\n",
424 | "import math\n",
425 | "\n",
426 | "# Time (hours)\n",
427 | "t = np.arange(0, 24)\n",
428 | "\n",
429 | "# Exterior temperature (K)\n",
430 | "t_out = 270 + 10 * np.sin( math.pi / 12 * (t - 6))\n",
431 | "\n",
432 | "# Interior temperature (K)\n",
433 | "t_in = np.full(24, 293)\n",
434 | "\n",
435 | "print(\"Time (hours):\", t)\n",
436 | "print(\"Outdoor Temperature (K):\", t_out)\n",
437 | "print(\"Indoor Temperature (K):\", t_in)\n",
438 | "\n",
439 | "U = 2 # Heat transfer coefficient (W/m²K)\n",
440 | "A = 100 # Heat transfer surface area (m²)\n",
441 | "\n",
442 | "# Electrical power (W)\n",
443 | "p_elec = U * A * (t_in - t_out) * (1 - t_out / t_in)\n",
444 | "\n",
445 | "print(\"Electrical Power (W) each hour:\", p_elec)\n",
446 | "\n",
447 | "e_elec = 3600 * p_elec\n",
448 | "\n",
449 | "print(\"Electrical Energy (J) each hour:\", e_elec)\n",
450 | "\n",
451 | "# Total Energy Usage (J)\n",
452 | "total_energy_use = np.sum(e_elec)\n",
453 | "\n",
454 | "# Total 24-hour cost (£)\n",
455 | "total_cost = total_energy_use * 0.0000000714\n",
456 | "\n",
457 | "print(\"Total 24-hour cost (£):\", total_cost)"
458 | ]
459 | },
460 | {
461 | "cell_type": "markdown",
462 | "metadata": {
463 | "id": "Oy9u2_oqeaxo"
464 | },
465 | "source": [
466 | "## Extension: Vector and Matrix Operations\n",
467 | "\n",
468 | "NumPy is designed to hold vectors, matrices, tensors and so on. It also contains functions to perform common operations relevant to these data types. For instance:"
469 | ]
470 | },
471 | {
472 | "cell_type": "code",
473 | "execution_count": null,
474 | "metadata": {
475 | "id": "8_l_TivNHFk3"
476 | },
477 | "outputs": [],
478 | "source": [
479 | "import numpy as np\n",
480 | "\n",
481 | "matrix = np.arange(4).reshape([2,2])\n",
482 | "vector1 = np.array([1,2])\n",
483 | "vector2 = np.array([3,4])\n",
484 | "\n",
485 | "print(\"Matrix: \", matrix)\n",
486 | "print(\"Vector1: \", vector1)\n",
487 | "print(\"Vector2: \", vector2)\n",
488 | "\n",
489 | "print(\"Normal: \", np.linalg.norm(vector1))\n",
490 | "print(\"Dot product\", np.dot(vector1, vector2))\n",
491 | "print(\"Matrix-vector multiplication: \", np.matmul(matrix, vector1))\n",
492 | "print(\"Matrix-matrix multiplication: \", np.matmul(matrix, matrix))\n",
493 | "print(\"Matrix power: \", np.linalg.matrix_power(matrix, 3))\n",
494 | "print(\"Determinant: \", np.linalg.det(matrix))\n",
495 | "print(\"Transpose: \", np.transpose(matrix))\n",
496 | "print(\"Inverse: \", np.linalg.inv(matrix))\n",
497 | "print(\"Eigenvectors and eigenvalues: \", np.linalg.eig(matrix))"
498 | ]
499 | },
500 | {
501 | "cell_type": "markdown",
502 | "metadata": {
503 | "id": "prYsG9Ssnx5N"
504 | },
505 | "source": [
506 | "## Extension Exercise: Cartesian Coordinates\n",
507 | "\n",
508 | "A location in 3d Cartesian space may be represented by (x,y,z) coordinates. This may be represented by a\n",
509 | "dimension 1 array with size 3.\n",
510 | "\n",
511 | "In the cell below:\n",
512 | "* Create a 1d array with three elements to represent Position A, which is at (1,2,1)\n",
513 | "* Calculate the location of Position B, which has a displacement of (3,-4,1) from Position A\n",
514 | "* Calculate the location of Position C, which is twice as far from the origin as Position B\n",
515 | "* Calculate the location of position D, which is found by rotating position C $45^{o}$ around the z axis (anti-clockwise\n",
516 | "when viewed from above). To rotate a location around the z axis in this manner by an angle $\\theta$, it may be\n",
517 | "multiplied by the matrix:\n",
518 | "$\n",
519 | "\\begin{pmatrix}\n",
520 | "\\cos(\\theta) & -\\sin(\\theta) & 0 \\\\ \n",
521 | "\\sin(\\theta) & \\cos(\\theta) & 0 \\\\ \n",
522 | "0 & 0 & 1\n",
523 | "\\end{pmatrix}\n",
524 | "$\n",
525 | "Remember that functions for sine and cosine typically take arguments in radians, not degrees.\n",
526 | "* Calculate the straight line distance between Position D and the origin"
527 | ]
528 | },
529 | {
530 | "cell_type": "code",
531 | "execution_count": null,
532 | "metadata": {
533 | "id": "lykqChQktPed"
534 | },
535 | "outputs": [],
536 | "source": []
537 | },
538 | {
539 | "cell_type": "code",
540 | "execution_count": null,
541 | "metadata": {
542 | "cellView": "form",
543 | "id": "ZRj4xpGYXLnD"
544 | },
545 | "outputs": [],
546 | "source": [
547 | "#@title\n",
548 | "import numpy as np\n",
549 | "import math\n",
550 | "\n",
551 | "# Define Position A\n",
552 | "pos_a = np.array([1,2,1])\n",
553 | "print(\"Position A: \", pos_a)\n",
554 | "\n",
555 | "# Add the specified displacement to position A to get position B\n",
556 | "pos_b = pos_a + np.array([3,-4,1])\n",
557 | "print(\"Position B: \", pos_b)\n",
558 | "\n",
559 | "# Double the values in position B to get position C\n",
560 | "pos_c = pos_b * 2\n",
561 | "print(\"Position C: \", pos_c)\n",
562 | "\n",
563 | "# Calculate 45 degrees in radians\n",
564 | "radians_45 = 45 * math.pi / 180\n",
565 | "# Create the rotation matrix\n",
566 | "rotation_matrix = np.array([[math.cos(radians_45), -math.sin(radians_45), 0], [math.sin(radians_45), math.cos(radians_45), 0], [0,0,1]])\n",
567 | "print(\"Rotation matrix: \", rotation_matrix)\n",
568 | "# Operate of position C with the rotation matrix to get position D\n",
569 | "pos_d = np.matmul(rotation_matrix, pos_c)\n",
570 | "print(\"Position D: \", pos_d)\n",
571 | "\n",
572 | "# Calculate the distance between position D and the origin\n",
573 | "distance_d = np.linalg.norm(pos_d)\n",
574 | "print(\"Distance: \", distance_d)"
575 | ]
576 | },
577 | {
578 | "cell_type": "markdown",
579 | "metadata": {
580 | "id": "2cfPmlVeD5xN"
581 | },
582 | "source": [
583 | "## Extension: Solving Matrix Equations and Sparse Matrices\n",
584 | "\n",
585 | "Sometimes it can be desirable to solve a matrix equation of the form $Mx=b$ where $M$ is a matrix, $b$ is a known vector and $x$ is an unknown vector whose value is to be found. For instance, consider the equation:\n",
586 | "\n",
587 | "$$\n",
588 | "\\begin{pmatrix}\n",
589 | "1 & 2 & 3\\\\ \n",
590 | "2 & 1 & 0\\\\ \n",
591 | "4 & 2 & 1\n",
592 | "\\end{pmatrix}\n",
593 | "\\vec{x}\n",
594 | "=\n",
595 | "\\begin{pmatrix}\n",
596 | "4\\\\ \n",
597 | "5\\\\ \n",
598 | "10\n",
599 | "\\end{pmatrix}\n",
600 | "$$\n",
601 | "\n",
602 | "We can use the ```linalg.solve``` function to solve an equation of this type as follows:"
603 | ]
604 | },
605 | {
606 | "cell_type": "code",
607 | "execution_count": null,
608 | "metadata": {
609 | "id": "Wu0240pJEAdt"
610 | },
611 | "outputs": [],
612 | "source": [
613 | "import numpy as np\n",
614 | "\n",
615 | "m = np.array([[1,2,3], [2,1,0], [4,2,1]])\n",
616 | "b = np.array([4,5,10])\n",
617 | "\n",
618 | "x = np.linalg.solve(m, b)\n",
619 | "\n",
620 | "print(x)"
621 | ]
622 | },
623 | {
624 | "cell_type": "markdown",
625 | "metadata": {
626 | "id": "b-TUc1BYIQXk"
627 | },
628 | "source": [
629 | "However, this function becomes slower and slower as the size of matrix gets larger. For a particular type of matrix that contains mostly zeros, this can be sped up by storing the matrix as a sparse matrix. This means that only non-zero values will be stored and only these non-zero values will be used in calculations. This dramatically reduces memory usage and the computational cost of the solving the matrix equation. There are may [types of sparse matrix included in SciPy](https://docs.scipy.org/doc/scipy/reference/sparse.html), but we will pick the [compressed row storage matrix](https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html#scipy.sparse.csr_matrix) for this example (see [here](https://en.wikipedia.org/wiki/Sparse_matrix#Compressed_sparse_row_(CSR,_CRS_or_Yale_format) ) for a rough description of how this works). The matrix we will solve for will have the value of 1 along the central diagonal and -0.5 on the adjacent diagonals. The array on the left-hand side will have a value of 1.\n",
630 | "\n",
631 | "There are [many solvers](https://docs.scipy.org/doc/scipy/reference/sparse.linalg.html) included to be used with sparse matrices. Which one to use is not always a simple question. For this example we'll use the [conjugate gradient solver](https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.cg.html#scipy.sparse.linalg.cg) (see [here](https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.cg.html#scipy.sparse.linalg.cg) for a description of how this method works)."
632 | ]
633 | },
634 | {
635 | "cell_type": "code",
636 | "execution_count": null,
637 | "metadata": {
638 | "id": "PWefU8KNFKHb"
639 | },
640 | "outputs": [],
641 | "source": [
642 | "import numpy as np\n",
643 | "from scipy.sparse import csr_matrix\n",
644 | "from scipy.sparse.linalg import cg\n",
645 | "import matplotlib.pyplot as plt\n",
646 | "\n",
647 | "# Provide the dimensions of the matrix as a tuple\n",
648 | "m = csr_matrix((1000, 1000))\n",
649 | "\n",
650 | "# Write values to the lead diagonal\n",
651 | "for i in range(1000):\n",
652 | " m[i,i] = 1\n",
653 | "\n",
654 | "# Write values to the diagonals next to the lead diagonal\n",
655 | "for i in range(999):\n",
656 | " m[i, i+1] = -0.5\n",
657 | " m[i+1, i] = -0.5\n",
658 | "\n",
659 | "print(\"M:\")\n",
660 | "# When we print the matrix, the coordinates of the non-zero values and their values will be printed\n",
661 | "print(m)\n",
662 | "\n",
663 | "b = np.zeros(1000) + 1\n",
664 | "print(\"B: \", b)\n",
665 | "\n",
666 | "x = cg(m, b)\n",
667 | "\n",
668 | "# Note the zero that is included in the tuple that was returned is an integer which indicates teh conjugate gradient solver converged correctly and found a solution\n",
669 | "print(\"X: \", x)\n",
670 | "\n",
671 | "# Plot the output with matplotlib\n",
672 | "# Don't worry if you haven't seen this before\n",
673 | "plt.plot(x[0])"
674 | ]
675 | },
676 | {
677 | "cell_type": "markdown",
678 | "metadata": {
679 | "id": "tEWHML18WGTP"
680 | },
681 | "source": [
682 | "This is an example of how Scipy can be used to solve linear algebra problems efficiently."
683 | ]
684 | }
685 | ],
686 | "metadata": {
687 | "colab": {
688 | "authorship_tag": "ABX9TyOSGsmoKYdTnuz0ywrUaRmc",
689 | "collapsed_sections": [],
690 | "name": "Array Operations.ipynb",
691 | "provenance": []
692 | },
693 | "kernelspec": {
694 | "display_name": "Python 3",
695 | "language": "python",
696 | "name": "python3"
697 | },
698 | "language_info": {
699 | "codemirror_mode": {
700 | "name": "ipython",
701 | "version": 3
702 | },
703 | "file_extension": ".py",
704 | "mimetype": "text/x-python",
705 | "name": "python",
706 | "nbconvert_exporter": "python",
707 | "pygments_lexer": "ipython3",
708 | "version": "3.13.7"
709 | }
710 | },
711 | "nbformat": 4,
712 | "nbformat_minor": 1
713 | }
714 |
--------------------------------------------------------------------------------