<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">#!/usr/bin/env python
# coding: utf-8

# # Computational Programming with Python
# ### Lecture 2: Slicing, Strings, Functions and Plots
# 
# ### Center for Mathematical Sciences, Lund University
# Lecturer: Claus FÃ¼hrer, Malin Christersson, `Robert KlÃ¶fkorn`, Viktor Linders
# 

# # This lecture
# 
# - Training exercises
# - More about lists and operators
# - String formatting
# - Functions
# - Modules
# - More about plots
# - Fixed point iterations
# - Jupyter notebooks

# ## Revision &amp;hyphen; lists
# 
# Given a list `L` you can use the *function* `len(L)` to find the length of the list.

# In[ ]:


L = [4, 9, -1, 2]
print("The length is", len(L))


# You can also use the *method* `append` by using dot notation after the name of the list.

# In[ ]:


L.append(11)
print("L =", L)


# ## Some more functions
# 
# Given a list `lst`:
# 
# - `sum(lst)`
# - `max(lst)`
# - `min(lst)`

# In[ ]:


L = [4, 9, -1, 2]
print("The sum is", sum(L))
print("The minimum value is", min(L))
print("The maximum value is",max(L))


# ## Min and max for strings
# 
# When comparing strings with the `&gt;` or `&lt;` operation then first the leading character is compared, which follows the ordering in the [UTF-8](https://en.wikipedia.org/wiki/UTF-8) character set. Afterwards other values such as string length are used. 

# In[ ]:


L = ['!adbg', 'Berlin', 'Berl']
print(min(L))
print(max(L))


# ## Some more methods
# 
# Given a list `lst`:
# 
# - `lst.reverse()`
# - `lst.sort()`
# - `lst.sort(reverse=True|False, key=myFunc)`
# 

# In[ ]:


L = [4, 9, -1, 2]
print("L =", L)
L.reverse()
print("L =", L)
L.sort()
print("L =", L)


# ## More about lists
# 
# We can access elements of a list by using square brackets.

# In[ ]:


L = ['C', 'l', 'o', 'u', 'd']
print(L[0])
print(L[-1])


# In a similar way we can access sublists.

# ## Creating sublists
# 
# ### Slicing
# **Slicing** a list between index $i$ and $j$ is forming a new list by taking elements starting at $i$ and ending *just before* $j$.
# 
# #### Example

# In[ ]:


L = ['C', 'l', 'o', 'u', 'd']
print(L[1:4])


# Compare with `range`.

# ## Partial slicing
# 
# One may omit the first or the last bound of the slicing:

# In[ ]:


L = ['C', 'l', 'o', 'u', 'd']
print("L[1:] =", L[1:])    # to the end
print("L[:3] =", L[:3])    # from the start
print("L[-2:] =", L[-2:])  # negative index
print("L[:-2] =", L[0:-2])


# ### Mathematical analogy
# 
# The interval $[-\infty, a)$ means: take all numbers strictly lower than $a$, cf. syntax of `L[:j]`.

# ## To sum it up
# 
# - `L[i:]` take all elements *except* the $i$ first ones
# - `L[:i]` take the first $i$ elements
# - `L[-i:]` take the last $i$ elements
# - `L[:-i]` take all elements *except* the $i$ last ones

# ## Examples
# 
# From a list `L`, create the red sublists. **5 min, let's go!**
# 
# ![lists](http://cmc.education/slides/notebookImages/lists.svg)
# 

# ## Basic string formatting
# Book p. 43
# 
# - Using keywords:

# In[ ]:


str1 = "I'm a {something}.".format(something = "string")
str2 = "I'm a {something}.".format(something = 10)
print(str1)
print(str2)


# - Using positional arguments:

# In[ ]:


print("Two strings, {} and {}.".format("a", "b"))


# ## f-strings
# 
# An elegant alternative in modern Python is the use of `f-strings`.

# In[ ]:


quantity = 33.45
other_quantity = 2
text = f"""
We use {quantity} g sugar and {other_quantity} g salt.
In total {quantity + other_quantity} g."""
print(text)


# To enable this interpretation of the text enclosed in {...} the string has to prefixed by the letter 'f'.
# 
# This feature is only included in the 2nd edition of the course book.

# ## Old string formatting
# 
# - for strings

# In[ ]:


course_code = "NUMA01"
print("This courseâ€™s code is %s" % course_code)


# - for integers

# In[ ]:


nb_students = 16
print("There are %d students" % nb_students)


#  - for floats

# In[ ]:


average_grade = 3.4
print("Average grade: %f" % average_grade)


# 
# ## Formatting numbers using f-strings
# 
# We can right-align numbers at some position which is written after a colon.

# In[ ]:


print("number      square")
print("------------------")
for i in range(-2, 3):
    print(f"{i:6} {i*i:10}")


# ## Alignment and decimals
# 
# We can add `.2f`to show two decimals:

# In[ ]:


print("number      square")
print("------------------")
for i in range(-2, 3):
    print(f"{i:6.2f} {i*i:10.2f}")


# ## Decimals and scientific notation
# 
# More examples

# In[ ]:


quantity = 33.45
print(f"five decimals: {quantity:0.5f}")
print(f"scientific notation with two decimals: {quantity:0.2e}")


# ## Basics on functions
# 
# Consider the following mathematical function:
# 
# $$ f(x) = 2x +1 $$
# 
# The Python equivalent is:

# In[ ]:


def f(x):
    return 2*x + 1


# - the keyword `def` tells Python we are defining a function
# - f is the name of the function
# - x is the *parameter*, or *input* of the function
# - what follows `return` is the *output* of the function

# ## Calling a function
# 
# Once the function is defined:

# In[ ]:


def f(x):
    return 2*x + 1


# it may now be called using:

# In[ ]:


a = f(1)
print(a)
print(f(2))


# The **parameter** $x$ is replaced by the **argument** 2.

# ## Functions with more than one parameter
# 
# Functions can have more than one parameter. 

# In[ ]:


def f(x,y):
    return 2*x + y


# it may now be called using:

# In[ ]:


a = f(1,2)
print(a)
print(f(2,5))


# ## Writing scripts
# 
# Collect a sequence of commands in a file with the file extension `.py`, e.g. `smartscripts.py`.

# In[ ]:


from numpy import *
def f(x):
    return 2*x + 1

z = []
for x in range(10):
    if f(x) &gt; pi: 
        z.append(x)
    else: 
        z.append(-1)
print(z)


# From IPython: `run smartscript.py`.

# ## Example &amp;hyphen; collecting functions
# 
# Create a **module** by collecting functions into a single file, e.g. `smartfunctions.py` as:

# In[ ]:


def g(x):
    return x**2 + 4 * x - 5

def h(x): 
    return 1/f(x)

def f(x):
    return 2 * x + 1


# - These functions can be used by an external script or directly in the IPython environment.
# - Functions within the module can depend on each other.
# - Grouping functions with a common theme or purpose gives modules that can be shared and used by others.

# ## Using modules
# 
# Modules need to be imported

# In[ ]:


import smartfunctions 
print(smartfunctions.f(2))


# In[ ]:


import smartfunctions as sf
print(sf.f(2))


# In[ ]:


from smartfunctions import g 
print(g(1))


# In[ ]:


from smartfunctions import * 
print(h(2)*f(2))


# ## More about plots
# 
# - You have already used the command `plot`. It needs a list of $x$ values and a list of $y$ values. If a single list, $y$ is given, the list `list(range(len(y)))` is assumed as $x$ values.
# - You may use the keyword argument `label` to give your curves a name, and then show them using `legend`.

# In[ ]:


from numpy import *
from matplotlib.pyplot import *
x_vals = [.2*n for n in range(20)] 
y1 = [sin(.3*x) for x in x_vals] 
y2 = [sin(2*x) for x in x_vals] 
plot(x_vals ,y1, label='0.3') 
plot(x_vals, y2, label='2')
legend ()
show() # not always needed.


# ## Plots using numpy arrays
# 
# The numpy function
# 
# `linspace(start, stop, number_of_points)` 
# 
# can be used to create evenly spaced points as a numpy array.
# 
# You can use *element wise* operations on numpy arrays.

# In[ ]:


x = linspace(0, pi, 20) # this is a numpy array of values
y1 = sin(.3*x) # the function is applied for each value in the array
y2 = sin(2*x)
plot(x ,y1, label='0.3') 
plot(x, y2, label='2')
legend ()
show() # not always needed.


# ## A plot with more key words
# 
# Here is an example with more keywords:

# In[ ]:


plot(x, y2, 
     color='green',
     linestyle='dashed', 
     marker='o', 
     markerfacecolor='blue', 
     markersize=12,
     linewidth=6)


# ## Fixed point iterations
# 
# A recursive equation can be written as 
# 
# \begin{cases}
# a_0 &amp;= c \\
# a_{n+1} &amp;= f(a_n), n \ge 0
# \end{cases}

# In[ ]:


def f(x):
    return 1. + 1./x

a = -3             # some initial value, e.g. -3
n = 100            # for some n
for i in range(n): 
    a = f(a)       # where f is some function

print(a)    


# A number $x_0$ such that $f(x_0)=x_0$ is called a *fixed point*. 
# 
# The sequence will converge to that fixed point if $f$ is a contraction ($|f(x) - f(y)| &lt; \alpha |x - y| \ \forall x,y$ and $\alpha \in [0,1)$) and a self map (e.g. $f: \mathbb{R} \to D \subset \mathbb{R}$) onto a closed set (see [Banach's fixed point theorem](https://en.wikipedia.org/wiki/Banach_fixed-point_theorem)). 
# 
# An example is visualised using a **cob web diagram**, see [www.geogebra.org/m/za7tarfg](https://www.geogebra.org/m/za7tarfg).
# Here you see that a fixed point can be **repelling** or **attracting**. If the initial value $c$ is \"close to\" an attracting fixed point ($f$ is a contraction), the sequence will converge to that fixed point.

# # What are Floating Point Numbers?

# # Floating point numbers -- The number e 
# 
# The number $e$ can be defined as 
# 
# $$e = \lim_{n\to\infty} \Big ( 1 + \frac{1}{n} \Big )^n.$$

# In[6]:


from numpy import *

e = exp(1)
print(e)

n = 1000000000
e = (1 + 1/n)**n
print(e)


# ## How to create a Floating Point Number?
# 
# - By typing it:

# In[ ]:


a = 3.012
b = 30.12e-1
c = 0.3012e+1
print(a)
print(b)
print(c)


# Note that 3.012 == 30.12e-1. The decimal point `floats` depending on the exponent.
# 
# - By typecasting from integer

# In[ ]:


n = 1        # This is an integer
a = float(n) # This is a float
print(n)
print(a)


# - As a result from a computation

# In[ ]:


n = 2       # This is an integer
a = n/n     # This is a float
print(n)
print(a)


# ## Comparing floating point numbers
# 
# Operations on floating point numbers rarely return the exact result:

# In[3]:


print(0.4 - 0.3)


# This fact matters when *comparing* floating point numbers:

# In[4]:


0.4 - 0.3 == 0.1


# Never compare floats using `==`. Ask instead if they are `near`:

# In[5]:


abs((0.4 - 0.3) - 0.1) &lt; 1.e-12


# In[ ]:


from numpy import exp, inf, nan
print(exp(1000.))
a = inf
print(3 - a)
print(3 + a)
print(a + a)
print(a - a)
print(a / a)


# # Example for internal representation (simplified)
# 
# Roughly speaking, a floating-point number is represented by a fixed number of significant digits (the significand) and scaled using an exponent in some fixed base. 
# 
# The base for the scaling is normally $2$, $10$ or $16$. 
# 
# A number that can be represented exactly is of the following form:
# 
# $$ \mbox{significand} \times \mbox{base}^{\mbox{exponent}},$$ where `significand`, `base` and `exponent` are integers (which have a simple representation).
# 

# Example: 
# 
# $$1.2345 = \underbrace{12345}_{\mbox{significand}} \times \underbrace{10\phantom{x}}_{\mbox{base}}^{\overbrace{\phantom{x}-4}^{\phantom{xxxxxxxxxx}\mbox{exponent}}}$$

# Example: 
# 
# $$123.45 = \underbrace{12345}_{\mbox{significand}} \times \underbrace{10\phantom{x}}_{\mbox{base}}^{\overbrace{\phantom{x}-2}^{\phantom{xxxxxxxxxx}\mbox{exponent}}}$$
# 
# The `decimal point` is floating. 

# ## Internal representation
# 
# Floats are represented by three quantities:
# The *sign*, the *mantissa* and the *exponent*:
# $$
# \text{sign}(x) \left( x_0 + x_1 \beta^{-1} + \dots + x_{t-1} \beta^{-(t-1)} \right) \beta^k, \quad \beta \in \mathbb{N}, \quad x_0 \neq 0, \quad 0 \leq x_i &lt; \beta.
# $$
# 
# - $x_0, \dots, x_{t-1}$ is the mantissa,
# - $t$ is the length of the mantissa,
# - $\beta$ is the basis,
# - $k$ is the exponent with $|k| \leq k_{max}$.
# 
# Normalization $x_0 \neq 0$ makes the representation unique (and saves one bit in the case $\beta = 2$).

# ## Internal representation (cont.)
# 
# $0$ cannot be represented as a *normalized* float.
# 
# Two zeroes: $+0$ and $-0$.
# 
# The exponent has a special indicator to indicate when a number is not normalized.
# 
# On your computer for a normal float:
# - $\beta = 2$
# - $t = 52$
# - $k_{max} = 1023$ (which needs 10 bits)
# 
# Together with the sign and the full range of the exponent, this requires 64 bits.

# ## Internal representation (cont.)
# 
# The smallest positive representable number is
# $$
# \mathrm{fl_{min}} = 1 \cdot 2^{-1023} \approx 10^{-308}
# $$
# and the largest is
# $$
# \mathrm{fl_{max}} = 1.111 \dots 1 \cdot 2^{1023} \approx 10^{308}.
# $$
# 
# Floating point numbers are not equally spaced in $[0, \mathrm{fl_{max}}]$.
# 
# Gap at zero (caused by normalization $x_0 \neq 0$):
# 
# Distance between $0$ and the first positive number is $2^{-1023}$.
# 
# Distance between the first and the second is smaller by a factor $2^{-52}$.
# 
# ![floatz.png](floatz.png)
# 

# ## Infinite and Not a Number
# 
# There are in total $2(\beta-1)\beta^{t-1}(2k_{max} + 1) + 1$ floating point numbers, in short floating point numbers are limited.
# 
# Numbers bigger than the maximal floating point number $\mathrm{fl_{max}}$ generate in numpy an `inf` or an overflow exception.
# 
# Numbers smaller than the minimal floating point number $\mathrm{fl_{min}}$ generate subnormalized numbers or `not a number`, short `nan`. This leads to unexpected behavior.

# In[ ]:


x = nan
print(x &lt; 0)
print(x &gt; 0)
print(x == x)


# The float `inf` behaves much more as expected:

# In[ ]:


print(0 &lt; inf)
print(inf &lt;= inf)
print(inf == inf)
print(-inf &lt; inf)
print(inf - inf)
print(exp(-inf))
print(exp(1/inf))


# ## Precision -- Machine epsilon
# 
# ### Definition (Maching epsilon):
# 
# The *machine epsilon* or rounding unit is the largest number $\epsilon$ such that
# $$
# \mathrm{float}(1.0 + \epsilon) = 1.0.
# $$
# 
# **Challenge**: Write a program which finds this number (or a good estimate for it).
# 
# See also
# 
# 

# In[ ]:


import sys
sys.float_info.epsilon


# ## What precision do I have at what number 
# 
# What decimal precision do I have at a given number $a \in \mathbb{R}$?
# 
# To figure out the precision we have at a number, we find the interval such that $a \in [2^k, 2^{k+1})$.
# We then subdivide that range using the `mantissa` bits.
# 
# Example: $3.5 \in [2,4)$. A float in Python has 52 bits of `mantissa`, so the precision we have at 3.5 is:
# 
# $$\frac{4-2}{2^{52}} = \frac{2}{4503599627370496} \approx 0.00000000000000044408921$$
# 
# $3.5$ itself is actually exactly representable by a float, but the amount of precision numbers we have at that scale is that value. The smallest number you can add or subtract to a value between $2$ and $4$ is that value. That is the resolution of the values you are working with when working between $2$ and $4$ using a float.

# What decimal precision do I have at $13689.0$? **5min, let's go!**

# ## Jupyter Notebooks
# 
# A Jupyter notebook combines text and code.
# 
# You can make notebooks using Anaconda.
# 
# For collaborative writing (online) you can use [Google colab](https://colab.research.google.com/notebooks/welcome.ipynb).
# 
# Other options are:
# - github.com or gitlab.com
# - dropbox
# - ...
# 
# 

# ## Notebooks basics
# 
# From Anaconda launch Jupyter Notebook (a server is started).
# 
# Choose a directory and click on `New -&gt; Python 3`. 
# 
# Rename the Notebook at the top. Write some code. Click on &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;enter&lt;/kbd&gt; to execute.
# 
# &lt;img src="http://cmc.education/slides/notebookImages/notebook1.png" alt="notebook1" align="center" style="border:2px solid #ccc; border-radius: 5px;"/&gt;

# ## Notebooks &amp;hyphen; code
# 
# Imports, variables, functions, etc. that you make in cell, are available in succeeding cells (if the cell has been executed)

# In[ ]:


from numpy import *
from matplotlib.pyplot import *

# use a magic function for rendering matplotlib plots
get_ipython().run_line_magic('matplotlib', 'inline')
x = linspace(0, 4*pi, 100)
y = sin(x)


# In[ ]:


plot(x, y)


# ## Notebooks &amp;hyphen; code (cont)
# 
# Under the menu `Kernel` you can `Restart &amp; Clear Output` or `Restart &amp; Run All`.
# 
# Using the tools in the toolbar, you can move cells up or down.
# 
# To delete a cell use the scissors tool.
# 
# &lt;img src="http://cmc.education/slides/notebookImages/notebook2.png" alt="notebook2" align="center" style="border:2px solid #ccc; border-radius: 5px;"/&gt;

# ## Notebooks &amp;hyphen; text
# 
# By default a cell is made for code. To write text, change the cell to "Markdown".
# 
# The text is formatted using markdown syntax.
# 
# The text in a cell is formatted when pressing &lt;kbd&gt;shift&lt;/kbd&gt;+&lt;kbd&gt;enter&lt;/kbd&gt;.

# ## Basic markdown syntax

# ```
# # Main heading
# 
# ## Smaller heading
# 
# ### Even smaller
# 
# **bold text**
# 
# *text in italics*
# 
# An unnumbered list:
# - item 1
# - item 2
# 
# A numbered list:
# 1. item 1
# 2. item 2
# ```

# ## LaTeX formulas
# 
# You make an inline formula, like $f(x) = x^2$, by enclosing LaTeX-code between single dollar signs.
# 
# You make a display style formula by enclosing the code between double dollar signs:
# 
# $$\int_0^\pi\!\sin(2x)\,dx$$

# ## Interactive slideshows (like this one)
# 
# To make interactive slideshows, you must install RISE.
# 
# For more information see [anaconda.org/conda-forge/rise](anaconda.org/conda-forge/rise)
</pre></body></html>