Functions are used everywhere and are very useful.

- Reusing code blocks
- Organizing code
- Many libraries provide and require functions

**Think of a function f(x) like a machine:**

- You put something in:
`x`

- You operate on
`x`

inside the function - The function returns some result
`f(x)`

.

**Keywords**

`def`

`return`

`:`

**Format**

```
def myFuncName(var_1, var_2, others):
# Insert any python code here
# You can create new variables, they can use var_1, var_2, etc.
# You can print things, load data from files, make plots, etc.
return XYZ # Often a function returns a value
# This is not required though
# XYZ can be be an expression, a variable you defined, etc.
```

Then we call the function like so:

```
myValue = myFuncName(5.5,8.8, etc)
```

Or define variables and use them in calling the function

```
x = 5.5
y = 8.8
z = myFuncName(x, y)
```

Or use the function where a value is wanted:

```
radius = 3
cylinder_volume = circle_area(radius)*height
```

Or:

```
print("cylinder volume =", height*circle_area(radius))
```

- Write a function called
`my_sum`

that takes two numbers as arguments and returns the sum. - Then call the function and store it as a variable.
- Then print the result

In [16]:

```
def my_sum(a, b):
return a + b
s = my_sum(5, 6)
print(s)
```

- Compare the various versions of sum functions below

In [1]:

```
def sum_1(x,y):
z = x + y
return z
my_sum = sum_1(5,6)
print("The sum of 5 and 6 is", my_sum)
```

In [20]:

```
def sum_2(x,y):
print("The sum of", x, "and", y, "is", x+y)
sum_2(5,6)
```

In [21]:

```
def sum_3(x,y):
return x+y
print( sum_3(5,6) )
```

In [22]:

```
def sum_4(x,y):
z = x+y
return z
print("This will not print: it's after the return")
print( sum_4(5,6) )
```

In [9]:

```
def sum_5(x,y):
the_sum = x+y
return the_sum
x = 6
y = 7
z = sum_5(x, y)
print("The sum of", my_x, "and", my_y, "is", my_z)
```

In [6]:

```
def add_and_multiply(x,y):
return x+y, x*y
add, prod = add_and_multiply(5,6)
print(add, prod)
```

- Remember the
`def`

and the`:`

and the`return`

- Return statements are optional, depending on what you want to do.
- We can call functions with arguments as values, like
`sum_3(5,6)`

. - We can create variables to hold values, then pass those variables as function arguments,
- like
`x=5 y=7 z = sum_5(x,y)`

- like
- The names of variables in the function argument list are independent of the variable names you pass to the function.
- If I write $f(x) = x^2$, it is fine to write $f(5)$.
- The function will take $x$ to be 5, then compute $5^2$.

- If I write $f(x) = x^2$, it is fine to write $a=3$, $f(a)$.
- The function will take $x$ to be $a$, then will compute $a^2$.
- Since $a=3$, the result will be $3^2$.

- If I write $f(x) = x^2$, it is fine to write $f(5)$.
- If the function returns a value, we can use it wherever a value is expected
- As in
`z = sum_3(x,y)`

- Or this
`print(sum_3(x,y))`

- As in

- Write a function called
`ft_to_m`

that will take a length in feet and convert it to a length in meters. - Use the converstion 1 m = 3.28084 ft.

In [1]:

```
def ft_to_m(L_in_ft):
return L_in_ft / 3.28084 # = L in m
L = 6.3 # ft
ft_to_m(L)
R = 8314.46 # J/(kmol*K)
```

- Write a function called
`P_IG`

that takes the number of moles, temperature, and volume as aguments, and returns the ideal gas pressure. - Try out the function.
- Don't forget to comment your code, including comments.
- Use good variable names.

In [2]:

```
def P_IG(n, T, V):
R = 8314.46 # J/kmol*K
P = n*R*T/V # Pa
return P
P = P_IG(1,300,1)
print(P)
```

A triple quoted string at the beginning of the function is a docstring that is output when getting "help" on the function.

In [3]:

```
def square_it(x):
"""
input: x
return: x**2
type "help(square_it)" to see these comments.
"""
return x*x
help(square_it)
```

In [4]:

```
def get_force(m, g=9.81) : # if not given, g defaults to 9.81
print("g = ", g)
return m*g
print( get_force(10) ) # no "g" --> g=9.81
print()
print( get_force(20,2) ) # g = 2, not 9.81
print()
print( get_force(20,g=4) ) # can be explicit about it
```

In [5]:

```
help(print)
```

- Rewrite your
`P_IG`

function above to use a default value for`Rg`

.

In [ ]:

```
```

**Type help(print) and see the default arguments.**

In [19]:

```
print(1,2,3, sep="\n")
```

In [9]:

```
def myfunc(a,b) :
print("a = ", a)
print("b = ", b)
myfunc(b=7,a=5) # note, calling with the opposite argument order
```

**Example**- For given $x_1$, and $x_2$, compute $S = f(x_1)+f(x_2)$ for any function $f$

In [6]:

```
#-------------- Define the sum function
def sum_func(f, x1, x2):
return f(x1) + f(x2)
#-------------- Define the functions to be summed
def f_a(x):
return 2*x
def f_b(x):
return x**0.5
#-------------- Define the values of x and call sum_func
x1 = 4
x2 = 9
print( sum_func(f_a, x1, x2) )
print( sum_func(f_b, x1, x2) )
```

- Write a function that computes the following integral for
*any*function $f(x)$ $$I = \int_a^bf(x)dx$$ - Evaluate the integral using the areas of two trapazoids.
- Find a midpoint $m=(a+b)/2$.
$$I = \underbrace{(m-a)\frac{f(a)+f(m)}{2}}_{\text{trapazoid 1}} +
`\underbrace{(b-m)\frac{f(m)+f(b)}{2}}_{\text{trapazoid 2}}$$`

- Try this on $I = \int_1^2x^2dx = 2.33333$
**Question**: what is the structure?- How many functions do you need to define?
- How to call the function?
- What order to define things in.
- After you have thought about this, look at the previous example and consider it's structure.

In [7]:

```
def integrate_f(f, a, b) :
m = 0.5*(a + b)
return (m-a)*(f(a) + f(m))/2 + (b-m)*(f(m) + f(b))/2
#---------- define a couple functions
def f_xSquared(x) :
return x**2.0
#----------- integrate the function
a = 1
b = 2
I = integrate_f( f_xSquared, a, b )
print("I =", I)
print("I_exact =", 2**3/3 - 1/3)
```

- Variables or functions defined inside a function are
**local**to that function and cannot be seen outside of it. - Variables or functions defined outside a function are
**global**and can be seen inside later functions.

In [8]:

```
a = 5.5 # a has global scope
def ff():
print("a = ", a) # a isn't passed in, but is known
ff()
```

In [9]:

```
a = 1.1
def ff2():
a = 2.2 # this is a NEW local a, not the same as above
print("inside: a = ", a)
ff2()
print("outside: a = ", a)
```

- Often you are given problems with many known parameters.
- It is normally best to put the parameters inside the function, rather than outside the function.
- This lets the function
*stand alone*. - (There may be reasonable exceptions to this rule though.)

Previously we used the following vapor pressure equation, with values of A, B, C given for Benzene. $$P_i^{sat}(T) = \exp\left(A_i - \frac{B_i}{T+C_i}\right)$$.

- A = 13.7819
- B = 2726.81
- C = 217.572

Code this function.

In [10]:

```
import numpy as np
#---------- DO THIS
def Psat_B(T):
A = 13.7819
B = 2726.81
C = 217.572
return np.exp(A - B/(T+C))
#---------- NOT THIS
A = 13.7819
B = 2726.81
C = 217.572
def Psat_B(T):
return np.exp(A - B/(T+C))
```

- If you write some code that uses functions that are not defined until later, it won't work, because python doesn't know about the functions yet.

In [11]:

```
a = 5.5
print(f_below(a)) # function that is not defined yet!
def f_below(x):
return x*x
```

**But note that the following works just fine.**

- function
`a`

uses function`b`

, but`b`

is defined below`a`

- But this works because both
`a`

and`b`

are defined when`a`

is actually**called**

In [25]:

```
def a(x):
return b(x)
def b(x):
return x**2
print( a(3) )
```

**Similarly, consider this example**

- The function uses
`g`

, which is not defined until after the function is defined. - Will this work?

In [17]:

```
def fma(m):
return m*g
g = 9.81
F = fma(100)
print(F)
```

- This works because g is defined when the function is
**called**, that is, it is defined at the time it is**needed**.

- Built-in Python functions
- There are tons of other functions available through Python libraries, like numpy

- anonymous (lambda) functions
- *args and **kwargs

In [9]:

```
help(print)
```

In [ ]:

```
```