# Scientific Computing: Introduction to Python


Dr. Ilian Iliev


Let's start with some examples of simple printing. Note that in Python 3 the brackets are required (not the case in Python 2). Quotes can be single or double.


```python
print('hello')
```

    hello



```python
print("hello")
```

    hello


## Using Python as a (quite powerful) calculator
You can print arithmetic expressions directly at the Python prompt and it will evaluate them and give you the result, just as a calculator will do. (Note: The pound sign (#) denotes the beginning of a comment – all characters between # and the end of the line are ignored 
by the Python interpreter.). For example:

```python
5+9
```




    14




```python
3**50
```




    717897987691852588770249L




```python
2000/25  #division
```




    80




```python
2000//25 #integer division
```




    80




```python
2345677*25
```




    58641925




```python
36**0.5  #square root (Note: sqrt() is not in core Python!)
```




    6.0


Note that (unlike many other programming languages) Python can do arbitrary-length arithmetic (Note: When the number has more digits than normal arithmetic Python 2 appends 'L' at the end of the result, for 'Long'; Python 3 does not do that.).


```python
45%7   # modulus (remainder)
```




    3



# Note: Beware of integer division in Python 2! 
Many people have been tripped by it. Try for example:

```python
print(3/2, 3/2.0, 3.0/2.0, float(3)/2, 3/float(2))
```

    (1, 1.5, 1.5, 1.5, 1.5)

The best solution is to make sure you always divide real numbers (floats). That is not an issue for Python 3, however.
# Exercise: Evaluate some more arithmetic expressions on your own. 
Core Python supports only a few mathematical functions, shown with some examples below

```python
a=-7
```


```python
b={3,9,2,55}
```


```python
c=3.1415
```


```python
abs(a)  #absolute value
```




    7




```python
max(b)  # Largest element in a sequence 
```




    55




```python
min(b)  # Smallest element in a sequence
```




    2


What about other mathematical functions like e.g. square root, logarithms, trigonometric functions, etc.? Those are not built into the core Python, but are available by loading the math module (we will also discuss other modules later). 

There are three ways of accessing the functions available in a module: 

```python
import math  # make the 'math' module available. This is the 
             # RECOMMENDED method because it is economical on resources
             # and a good programming practice. This is the method used
             # by most programmers.
```


```python
print(math.log(math.sin(0.5))) # Then this is how you use it - each 
                               # function gets 'math' prefix telling
                               # Python where to get this function from.
```

    -0.735166686385

You can also 'rename' modules when importing them, for brevity or other reasons:

```python
import math as m  # From now on module math has also this other name 'm'
```


```python
m.cosh(3)
```




    10.067661995777765


You can also import a module like this:

```python
from math import *   # import ALL math module functions
```
For small modules this could be OK, but for big ones with many functions like 'math' this is wasteful, and also a bad idea for other reasons. 

Finally, you can also just import specific functions that you need, in which case you can use the functions without a module prefix:

```python
from math import log, sin
```


```python
print(log(sin(0.5)))
```

    -0.735166686385

The contents of a module could be printed as follows (for example to check if a certain function is available). This type of inquiry is usually called 'introspection':

```python
dir(math)
```




    ['__doc__',
     '__file__',
     '__name__',
     '__package__',
     'acos',
     'acosh',
     'asin',
     'asinh',
     'atan',
     'atan2',
     'atanh',
     'ceil',
     'copysign',
     'cos',
     'cosh',
     'degrees',
     'e',
     'erf',
     'erfc',
     'exp',
     'expm1',
     'fabs',
     'factorial',
     'floor',
     'fmod',
     'frexp',
     'fsum',
     'gamma',
     'hypot',
     'isinf',
     'isnan',
     'ldexp',
     'lgamma',
     'log',
     'log10',
     'log1p',
     'modf',
     'pi',
     'pow',
     'radians',
     'sin',
     'sinh',
     'sqrt',
     'tan',
     'tanh',
     'trunc']


Many of the above functions should be familiar to you. If you are not sure what a given function do or how to call it, you can invoke the Python help system, as follows:

```python
help(modf)
```

    Help on built-in function modf in module math:
    
    modf(...)
        modf(x)
        
        Return the fractional and integer parts of x.  Both results carry the sign
        of x and are floats.
    


Exercises:

1) The Universe is 13.6 billion years old, how many seconds old is it?

2) Compute 2 to the 266th power. This is (roughly) the number of atoms 
in the Universe.

3) Import the math module and calculate: 

\begin{equation}
\cos(\pi/4)
\\ 
\pi-4 atan(1)
\end{equation}

Do results conform with your expectations?

# Variables and important data types
In Python, all numbers (and everything else, including functions) are 
OBJECTS (i.e. Python, like C++ is an object-oriented language). A variable can be used to store a certain value or object. There are several basic types of variables that we will need: integers, floats (real numbers), complex numbers, strings and combinations of these (lists, tuples and arrays). 

Variables are typed dynamically, i.e. their type (which can be checked with 'type(var)') is set whenever the variable is assigned a value and can be changed by assigning another value: 

```python
a=1  #integer
```


```python
type(a)
```




    int




```python
b=1.0 #float=real
```


```python
type(b)
```




    float




```python
c='1.0' #string
```


```python
type(c)
```




    str




```python
d=2+3.0j #complex number
```


```python
type(d)
```




    complex


A string is a sequence of characters enclosed in single or double quotes. Long strings continuing on several lines are defined using triple quotes. 

Strings are concatenated with the plus (+) operator, whereas slicing (:) is used to extract a portion of the string:

```python
string1 = 'Press return to exit'
```


```python
string2 = 'the program'
```


```python
print(string1 + ' ' + string2) # Concatenation
```

    Press return to exit the program



```python
print(string1[0:12])  # Slicing
```

    Press return

Strings are immutable objects (i.e. its individual characters cannot be modified with an assignment statement; the whole string CAN be assigned a different value) with a fixed length. 

An attempt to violate immutability will result in TypeError, as shown here:

```python
s='Press return to exit'
```


```python
s[0]  # Individual characters can be accessed
```




    'P'




```python
s[0]='T' # ... but they cannot be modified
```


```python
b=2; c=4 # b and c are of an integer type
```


```python
b=b*1.0; c=float(c) # two ways to convert them back into floats 
```


```python
print(b,c) # Both b and c are floats now, the original values were destroyed
```

    (2.0, 4.0)



```python
d=int(b); print(d) # 'int' converts a number into an integer
```

    2

The function 'round' with a single argument rounds up a number to the nearest integer (up or down). You can also round a number with certain number of significant digits after the decimal point.

Strings also can be converted into numbers if they are made of numbers: 

```python
a=5.78666; print(round(a)); print(round(a,3))
```

    6.0
    5.787



```python
s='357'; type(int(s))
```




    int




```python
print(int(s))
```

    357


# Exercises: 

1) Create a string variable 'myname' that is initialised to 
your full name - first, middle and last.

2) Using a slice operator, print your first name only.

3) Using a slice operator, print your last name only.

4) Using the slice and concatenation operators, print 
your name in the form 'Last name, First name'.

5) Create a new string where your middle name is replaced by
your middle initial.

6) Use Python to check how long the 'myname' string is and 
if it contains the letter 'a'.

7) What happens if you multiply a string and a number?

# Tuples and lists
A tuple is a sequence of arbitrary objects separated by commas and enclosed in parentheses. If the tuple contains a single object, a final comma is required; for example, 'x = (2,)'. Tuples support the same operations as strings and are also immutable. Here is an example where the tuple 'rec' contains another tuple '(6,23,68)':

```python
rec = ('Smith','John',(24,7,1988))   # This is a tuple
```


```python
lastName,firstName,birthdate = rec # Unpacking the tuple into its parts
```


```python
print(firstName)
```

    John



```python
birthYear=birthdate[2]; print(birthYear)
```

    1988



```python
name = rec[1] + ' ' + rec[0] # combine the two name strings
```


```python
print(name)
```

    John Smith



```python
print(rec[0:2]) # Slice the first 2 elements of the tuple 'rec'
```

    ('Smith', 'John')


### Important note: In Python sequences have zero offset, so that a[0] represents the first element of a, a[1] the second one, and so forth. Similarly, indices in Python always start from 0, so e.g. 2 is the third element, etc. 
Lists are ordered sets of objects similar to tuples, but they are mutable, i.e. their elements and length can be changed. A list is identified by enclosing its elements in square brackets, [,]. The objects in a list could be of any type, e.g. real or integer numbers, strings, even other lists.

Below is a sampling of operations that can be performed on lists: 

```python
a = [1.0,'two',3]  # Define a list
```


```python
a[0]  # First element of the list
```




    1.0




```python
a[1]  # Second element of the list
```




    'two'




```python
a[3]  # There is no such element, so this gives an error.
```


```python
a.append(33.0)  # Append another element (33.0) to the list
```


```python
print(a)
```

    [1.0, 'two', 3, 33.0]



```python
a.insert(0,22)  # Insert another element (22) at position 0
```


```python
print(a)
```

    [22, 1.0, 'two', 3, 33.0]

Question: What does a[-1] give? What about a[-3]?If a is a mutable object, such as a list, the assignment statement b=a 
does not result in a new object b, but simply creates a new reference to a (i.e. now the same object has 2 names - a and b). Thus any changes made to b will be reflected in a (but not vice-versa). To create an independent copy of a list a, use the statement 'c = a[:]', as shown below:

```python
b=a  # 'b' becomes an alias of a
```


```python
b[0]=5  # change an element of b
```


```python
print(a) # a has changed 
```

    [5, 1.0, 'two', 3, 33.0]



```python
c=a[:]  # 'c' is an independent copy of a
```


```python
c[0]=4  # change an element of c
```


```python
print(a) # a remains the same
```

    [5, 1.0, 'two', 3, 33.0]

There are various oprations that can be performed on sequences like lists. Some examples: 

```python
print(a[1:3]) # slices a part of a (here the second and third elements)
```

    [1.0, 'two']



```python
len(a)        # how many elements does 'a' have?
```




    5




```python
'two' in a   # checks if a certain element can be found in the list
```




    True




```python
a[2]=7       # lets make the list wholly of numbers (strings cannot be compared to numbers!)
```


```python
max(a)       # what is the largest element of the sequence?
```




    33.0




```python
min(a)       # what is the smallest element in the sequence?
```




    1.0




```python
d=a+c        # concatenates the two sequences
```


```python
print(d)
```

    [5, 1.0, 7, 3, 33.0, 4, 1.0, 'two', 3, 33.0]


# Comparison operators
The comparison operators are: 
< (less than), 
> (greater than), 
<= (less than or equal to), 
>= (greater than or equal to), 
== (equal to) 
!= (not equal to). 

They are quite useful for programming, as we shall see. The result is a logical expression ('True' or 'False'). These can be used to construct more complicated logical statements using also 'or' and 'and'. 

Numbers of different type (integer, floating point, etc.) are converted to a common type before the comparison is made. Otherwise, objects of different type are considered to be unequal (e.g. 2 does not equal 2.0). Here are a few examples:

```python
a=5.7
```


```python
b=4.3
```


```python
print(a>b)
```

    True



```python
print(a==b)
```

    False



```python
c=3
```


```python
a>b and c<b
```




    True



# Exercises:

1) What is the result of the Boolean expression 'not 8>5'?

2) What is the result of the Boolean expression 'not(True and False)'? Is this what you expected?

3) Write a compound Boolean expression that returns True if the value    of the variable 'count' is between 1 and 10 inclusive. 

# Conditionals
Conditionals are quite useful in construction of algorithms and are 
an important program control structure. The 'if' construct

if condition:
    block
    
executes a block of statements (which MUST be indented since that 
is how Python knows what is inside the 'if' and what is not!) if the 
condition returns true. If the condition returns false, the block 
is skipped. The if conditional can be followed by any number of 
'elif' (short for “else if”) constructs

if condition:
    block1
elif condition:
    block2

which work in the same manner. The 'else' clause

else:
   block

can be used to define the block of statements that are to be executed if none of the if-elif clauses is true. The function below illustrates the use of the conditionals:

```python
def sign_of_a(a):
    if a < 0.0:
        sign = 'negative'
    elif a > 0.0:
        sign = 'positive'
    else:
        sign = 'zero'
    return sign

a = 1.5
print('a is ' + sign_of_a(a))
```

    a is positive

Here 'def' defines a function, to be discussed below (similar to Matlab 
functions).
# Exercises: 

1) If you have several nested 'if/else'  constructs, how does Python 
know to which 'if' an 'else' belongs? Try to check your answer 
using an example.

2) Write a construct that sets the value of a variable called 
'grade' to the value 4 if a variable named 'score' is greater 
than 70, 3 if 'score' is between 60 and 69, 2 if 'score' is 
between 50 and 59, 1 if 'score' is between 40 and 49, and 0 otherwise. 
This statement (roughly) converts British-style grades to American-style ones.

# Loops 
One of the most typical tasks we use a computer for is to do a certain task multiple times (e.g. for different input values). Such repetitive tasks (at which computers are really good, since unlike people they do not get bored) are performed by loops. There are two basic types of loops - a 'while' loop and a 'for' loop. 
## 'While' loops
The 'while' construct:

while condition:
    block

executes a block of (again, indented) statements if the condition is true. After execution of the block, the condition is evaluated again. If it is still true, the block is executed again. This process is continued until the condition becomes false. The 'else' clause

else: 
    block

can be used to define the block of statements that are to be executed if the condition is false. 

Here is an example that creates the list [1, 1/2, 1/3, . . .]:

```python
nMax=5
```


```python
n=1
```


```python
a=[]  # Create empty list
```


```python
while n < nMax:
    a.append(1.0/n) # Append element to list
    n = n + 1
    print(a)
```

    [1.0]
    [1.0, 0.5]
    [1.0, 0.5, 0.3333333333333333]
    [1.0, 0.5, 0.3333333333333333, 0.25]

Note the indentations above - they indicate what does or does not belong to the loop (since there is no explicit loop end) and are thus absolutely necessary. The IPython shell will do the indentations for you automatically (you indicate the end the loop by pressing 'Return' twice), but an external editor might not do it for you, in which case you have to do it yourself. 
## The range() command

A special type of list is frequently required (often together with 
for-loops, see below) and therefore a command exists to generate that 
list: the 'range(stop)' command generates a list of integers starting 
from 0 and going up to but NOT INCLUDING n. You can also specify 
both the start and stop points, as well as (optionally) the step

range(start,stop[,step])

(see help for further details). Here are a few examples:


```python
for a in range(5):
    print(a)          # print a
```

    0
    1
    2
    3
    4



```python
for a in range(3,10,2): 
    print(a) 
```

    3
    5
    7
    9


# For loops
The 'for' loops are explicit in terms of how many times the loop statements will be executed, rather than conditional as the 'while' loops we discussed above:

for target in sequence:
    block of instructions
    
and repeat the operations in 'block' for all values in 'sequence'. Unlike many other programming languages (e.g. C, Fortran), the loops in Python need not be over an integer index (as were the 'range' examples above), but can be performed directly over the sequence elements, which need not even be numbers. Here is an example where we iterate over a list of strings and within it over the letters in each string:    

```python
fruits = ['apple','banana','orange']
```


```python
for fruit in fruits:
    for letter in fruit:
         print(letter)
```

    a
    p
    p
    l
    e
    b
    a
    n
    a
    n
    a
    o
    r
    a
    n
    g
    e

Python provides further statements for controlling the flow of a program: break, continue and else. 

The 'break' command issued inside a loop immediately ends the loop and moves execution to the statements immediately following that loop. 

```python
x=0
```


```python
while True: # this will go on forever unless stopped
    x += 1  # add 1 to x and put the result back in x
    if not (x % 15 or x % 25):
        break
print(x,  'is divisible by both 15 and 25')
```

    (75, 'is divisible by both 15 and 25')

The 'continue' statement allows us to skip a portion of the statements in an iterative loop. If the interpreter encounters the continue statement, it immediately returns to the beginning of the loop to start the next iteration without executing the statements below 'continue'. 

The following example compiles a list of all numbers between 1 and 99 that are divisible by 7:

```python
x = []  # Create an empty list
for i in range(1,100):
   if i%7!= 0: continue # If not divisible by 7, skip rest of loop
   x.append(i) # Append i to the list
print(x)
```

    [7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]

Finally, a 'for' or 'while' loop may be followed by an 'else' block of statements, which is executed only if the loop finished 'normally', i.e. without using 'break'.
# Exercises:

1) Write a for loop which calculates the first 10 terms of the Madhava series:

\begin{equation}
\sqrt{12}\left(1-\frac1{3\times3}+\frac1{5\times3^2}-\frac1{7\times3^3}+...\right)
\end{equation}

(this is one way to calculate $\pi$ using series).

2) Use a 'while' loop to calculate

\begin{equation}
x_{k+1}=\frac12\left(x_k+\frac2{x_k}\right)
\end{equation}  

with x(0)=1, until $abs(x(k+1)-\sqrt(2)) < 0.0001$. (As we will discuss later in the term, this is a way to calculate square roots using just addition and division). 
     
3) The double factorial function, n!!, is the product of the positive odd integers up to and including n (which must itself be odd):

\begin{equation}
 n!!=\prod\limits_{i=1}^{(n+1)/2}(2i-1)=1\cdot3\cdot5\dots(n-2)\cdot n
\end{equation}

Write a routine to calculate n!! in Python. 

4) A very elegant way to calculate the greatest common denominator of two numbers is the ver elegant Euclid agorithm:

In [7]: a,b=1071,462

In [8]: while b:

            a,b=b,a%b
        

In [9]: print a

21

Explain how it works.

# Reading input and writing output
The intrinsic Python 2.x function for accepting user input is

raw_input(prompt)

Note: In Python 3.x this function is renamed 'input()'. In the Python 3.x version of our textbook (Kyusalaas) this is incorrect and 'raw_input' is still being used.

The 'raw_input' function displays the prompt and then reads a line of input that is converted into a string. To convert the string back into a numerical value (if it is a number), you can use the function

eval(string)

```python
a = raw_input("Input a: ") # Ask for input
```

    Input a: 34



```python
print(a,type(a))  # Print a and its type
```

    ('34', <type 'str'>)



```python
b=eval(a)  # b is a number
```


```python
print(b,type(b))  # 'b' is a number (integer)
```

    (34, <type 'int'>)

As we saw earlier, output can be displayed with the print statement:

print(object1, object2, . . .)

which converts object1, object2, and so on to strings and prints them on the same line, separated by spaces. The newline character '\n'  can be used to force a new line. For example,

```python
a=1234.56789
```


```python
b=[2,4,6,8]
```


```python
print(a,b)
```

    (1234.56789, [2, 4, 6, 8])



```python
print 'a=',a,'\nb=',b
```

    a= 1234.56789 
    b= [2, 4, 6, 8]

The modulo operator '%' can be used to format a tuple. The form of the conversion statement is

'%format1 %format2 ... ' % tuple

where format1, format2 ... are the format specifications for each object in the tuple. Typically used format specifications are:

wd      Integer
w.df    Floating point notation
w.de    Exponential notation

where 'w' is the width of the field and d is the number of digits after the decimal point. The output is right-justified in the specified field and padded with blank spaces (there are provisions for changing the justification and padding). A few examples:

```python
n=9876
```


```python
print('%7.2f'%a)
```

    1234.57



```python
print('n=%6d'%n)  # Pad with spaces
```

    n=  9876



```python
print('n=%06d'%n) # Pad with zeroes
```

    n=009876



```python
print('%12.4e %6d' %(a,n))
```

      1.2346e+03   9876


# Exercise:
Calculate and print on the screen a temperature conversion table from Fahrenheit to Celsius scales. The table should include temperatures from -300 to 210 degrees Fahrenheit (in steps of 5 
degrees) and their Celsius equivalents, presented in 2 columns 
with appropriate headings. Each column should be 10 characters wide, and each temperature should have 3 digits to the right of the decimal point. The conversion formula is:

C=(F-32)(5/9)
# Reading from and writing to a file
Up to this point, all data we have used has been either hard-coded into the program or has been obtained from or written to the screen. However, often we need to read from an external file, or write it to such a file. This is done through Python 'file' objects.

A file object is created by opening a file with a given filename and mode. The filename could be given as an absolute path (from the root directory/folder up) or relative one (i.e. from the current working directory). 'Mode' is a string with one of the values given below, which indicates what the file will be used for (reading, writing, or both) and what type of data is being read/written (text, binary).

mode        meaning
r           text, read-only (the default)
w           text, write (an existing file with the same name will be overwritten)
a           text, append to an existing file
r+          text, reading and writing
rb          binary, read-only
wb          binary, write (an existing file with the same name will be overwritten)
ab          binary, append to an existing file
rb+         binary, reading and writing

For example:

```python
f=open('my_file.txt','w')   # open a text file for writing
```
file objects are closed with the 'close' method:

```python
f.close()
```
Note: Python automatically closes any open files when a program terminates.The 'write' method of a file object writes a string to the file (in Python3 it also returns the number of characters being written): 

```python
f=open('my_file.txt','w')   # open a text file for writing
```


```python
f.write('Hello')
```


```python
f.close()
```
To read n bytes from a file we use the 'read' method, f.read(n). If n is omitted , the entire file is read in (it is your problem if the file is too large, as stated in the official documentation).

'readline()' reads a single line from a file, up to nd including the newline ('\n') character. Each subsequent call to 'readline' reads another line from the file. Both 'read' and 'readline' return an empty string when they reach the end of the file. To read all of the lines into a list of strings in one go, use 'f.readlines()'. 

A better method in practice is to use Numpy to read and write data to files (to be discussed later).
# Functions
We have already encountered many internal Python functions. One can also define new, custom functions which behave the same way as the internal ones. The structure of a Python function is

def  func_name(param1,param2,...):
    statements
    return return_values
    
(again, note the indentations!), where 'param1, param2, ...' are the parameters. A parameter can be any Python object, including another function. Parameters may be given default values, in which case the parameter in the function call is optional. If the return statement or return values are omitted, the function returns the null object (i.e. nothing).

The following example computes numerically the first two derivatives of arctan(x) using finite differences (we will discuss this method later in the course, for now focus on the Python constructs only):

```python
from math import atan
def finite_diff(f,x,h=0.0001): # h has a default value
   df =(f(x+h) - f(x-h))/(2.0*h)
   ddf =(f(x+h) - 2.0*f(x) + f(x-h))/h**2
   return df,ddf

x = 0.5
df,ddf = finite_diff(atan,x) # Uses default value of h
print 'First derivative =',df
print 'Second derivative =',ddf
```

    First derivative = 0.799999999573
    Second derivative = -0.639999991892


Note that the function atan (arctan) was passed to the function finite_diff as a parameter.

The number of input parameters in a function definition may be left arbitrary. For example, in the function definition

def func(x1,x2,*x3)

'x1' and 'x2' are the usual parameters, also called positional parameters, whereas 'x3' is a tuple of arbitrary length containing the excess parameters (indicated by the star in front of x3). Calling this function with e.g.

func(a,b,c,d,e)

results in the following correspondence between the parameters:

\begin{equation}
a \leftrightarrow x1, b \leftrightarrow x2, (c,d,e) \leftrightarrow x3
\end{equation}

The positional parameters must always be listed before the excess parameters!
If a mutable object, such as a list, is passed to a function where it is then modified, the changes will also appear in the calling program. Here is an example:

```python
def squares(a):
    for i in range(len(a)):
       a[i] = a[i]**2

a = [1, 2, 3, 4]
squares(a)
print(a)  # the list a is now modified
```

    [1, 4, 9, 16]


# * syntax
Sometimes we want to call a function with arguments taken from a list 
or other sequence. The * syntax, used in a function call unpacks such a sequence into positional arguments to the function. 

Here is an example, using the function 'math.hypot(a,b)'  (this is a function, in the 'math' package, which for given 'a' and 'b' returns sqrt{a^2+b^2}. If the two arguments are in a list or tuple, the following call will fail:

```python
import math

t=[3,4]

math.hypot(t)
```
This is because we tried to call the function with a single (list) argument 't', while it requires two arguments. One can just index the list explicitly:

```python
math.hypot(t[0],t[1])
```




    5.0


which works fine, but a more elegant method is to unpack the object:

```python
math.hypot(*t)
```




    5.0



# Lambda  Statement
If a function has the form of an expression, it can be defined with the 
'lambda' statement:

func name = lambda param1, param2,...: expression

This is called an anonymous function (function literal) and useful for 
defining small helper function that will be used only once. Here is an example:

```python
c = lambda x,y : x**2 + y**2
print c(3,4)
```

    25


# Exercises:
1) A year is a leap year if it is divisible by 4, unless it is a century that is not divisible by 400. Write a function that takes a year as a parameter and returns 'True' if the year is a leap one and 'False' otherwise.

2) Write a function that takes two parameters - a pay rate and the number of hours worked -- and returns the total pay. Any hours over 40 paid at 1.5 times higher rate. 
492f0da340e06b8a3c6525b43e4cc56dcaf7dc4e
ipynb
Jupyter Notebook
ExamPrep/01 Intro to Python/__EXAMPLES 01 - Intro to Python.ipynb
FHomewood/ScientificComputing
bc3477b4607b25a700f2d89ca4f01cb3ea0998c4
IJG
ExamPrep/01 Intro to Python/__EXAMPLES 01 - Intro to Python.ipynb
FHomewood/ScientificComputing
bc3477b4607b25a700f2d89ca4f01cb3ea0998c4
IJG
ExamPrep/01 Intro to Python/__EXAMPLES 01 - Intro to Python.ipynb
FHomewood/ScientificComputing
bc3477b4607b25a700f2d89ca4f01cb3ea0998c4
IJG
Qwen/Qwen-72B
1. YES
2. YES