Functions#

\(\newcommand{\re}{\mathbb{R}}\)

A function is something which takes some input arguments and returns one or more output values. These are called def in Python, short for definition. They have following structure

def functionname(arg1,arg2,arg3):
    DO SOME COMPUTATION
    return result

The body of the function must be indented.

Whenever some piece of computation is to be repeated many times, it is best to put it inside a function. This avoids code duplication, which minimizes errors and makes it easy to maintain your code.

import numpy as np

Sum two vectors#

For \(x, y \in \re^n\), compute \(z \in \re^n\) by

\[ z_i = x_i + y_i, \qquad 0 \le i \le n-1 \]
def sum1(x,y):
    z = np.empty_like(x)
    for i in range(len(x)):
        z[i] = x[i] + y[i]
    return z

Now we can use this function.

n = 5
x = np.ones(n)
y = 2*x
z = sum1(x,y)
print(z)
print(z - (x + y))
[3. 3. 3. 3. 3.]
[0. 0. 0. 0. 0.]

Check your inputs#

We can add two vectors only if they are of same size. It is always a good practice to check that your inputs are consistent. We will also use enumerate and zip.

def sum2(x,y):
    assert len(x) == len(y)
    z = np.empty_like(x)
    for i,(a,b) in enumerate(zip(x,y)):
        z[i] = a + b
    return z

First test on same inputs as before.

z = sum2(x,y)
print(z)
print(z - (x + y))
[3. 3. 3. 3. 3.]
[0. 0. 0. 0. 0.]

If you try to run the following code, it will give an error.

n = 5
x = np.ones(n)
y = np.ones(n+1)
z = sum2(x,y)

Minimum value of an array#

Given \(x \in \re^n\), compute

\[ m = \min_{0 \le i \le n-1} x_i \]
def minval(x):
    val = x[0]
    for v in x:
        if v < val:
            val = v
    return val

Test on some random array.

x = np.random.rand(10)
mval = minval(x)
print('x = \n', x)
print('Minimum = ',mval)
print('Numpy   = ', x.min())
x = 
 [0.69297944 0.31715733 0.55422451 0.17249301 0.13807156 0.63522691
 0.32420193 0.14121678 0.31985492 0.7443537 ]
Minimum =  0.13807156091981854
Numpy   =  0.13807156091981854

Min and max of an array#

Given \(x \in \re^n\), compute

\[ m = \min_{0 \le i \le n-1} x_i, \qquad M = \max_{0 \le i \le n-1} x_i \]
def minmax(x):
    min_val = x[0]
    max_val = x[0]
    for v in x:
        if v < min_val:
            min_val = v
        if v > max_val:
            max_val = v
    return min_val, max_val

The type of returned object is tuple.

x = np.random.rand(5)
min_val, max_val = minmax(x)
print(x)
print('Minimum =',min_val,x.min())
print('Maximum =',max_val,x.max())
[0.96005078 0.57809825 0.7920731  0.4882905  0.61148067]
Minimum = 0.4882905019739978 0.4882905019739978
Maximum = 0.9600507849639945 0.9600507849639945

lambda: for simple functions#

If the function definition is simple we can define them using lambda

\[ f(x) = \sin(2\pi x) \]
f = lambda x: np.sin(2*np.pi*x)
x = np.linspace(0.0, 1.0, 100)
y = f(x)
min_val, max_val = minmax(y)
print('Minimum =',min_val)
print('Maximum =',max_val)
Minimum = -0.9998741276738751
Maximum = 0.9998741276738751

We can define lambda’s with more than one argument.

g = lambda x,y: np.sin(2*np.pi*x) * np.cos(2*np.pi*y)
z = g(1.2, 2.3)
print(z)
-0.2938926261462344

Variables inside functions are local#

def fun1(x):
    a = 1
    print(x + a)

def fun2(x):
    a = 2
    print(x + a)

def fun3(x):
    print(x + a)

The variable a inside these functions are local variables and have different values.

fun1(1)
fun2(1)
fun3(1)
2
3
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[13], line 3
      1 fun1(1)
      2 fun2(1)
----> 3 fun3(1)

Cell In[12], line 10, in fun3(x)
      9 def fun3(x):
---> 10     print(x + a)

NameError: name 'a' is not defined

Variable a is not visible in fun3 since it is a local variable in fun1 and fun2.

Beware of global variables#

p = 1.234 # Global variable, visible inside all functions.

def fun4(x):
    p = 1.0 # Local variable of same name
    print(x + p)

def fun5(x):
    print(x + p) # Here the global variable p is used

The variable p = 1.234 which is declared outside the functions is a global variable.

fun4(1.0)
fun5(1.0)
2.0
2.234

The variable p = 1.0 is a local variable in fun4; this is ok, you can have a local and global variable of the same name, they have independent existence. But in fun5, the global variable p = 1.234 is used since there is no local variable of the same name. If you wanted to use the global variable, then it is fine, but if you forgot to declare p inside fun5, then you have a bug, and it can be difficult to detect.

You have to be very careful with global variables. If you forget to declare some variable inside a function, and a global variable with the same name exists, then your program will run but likely give wrong answers. We should ideally never use global variables, but they are rampant in jupyter notebooks. To avoid global variables, you have to declare ALL variables inside functions and not declare anything in the global scope. It is good practice not to rely on global variables, but instead write pure functions.

Pass a function as argument to other functions#

def sin(x):
    return np.sin(x)
    
def cos(x):
    return np.cos(x)
    
def run(x, f):
    print(f(x))
x = np.pi/2
run(x, sin)
run(x, cos)
1.0
6.123233995736766e-17