Chapter 1: Basic Python Math#

1.1 Basic Math Operation#

Python can be used as a powerful calculator! Think of it like a super-charged graphing calculator that can handle both simple arithmetic and complex mathematical operations.

In Jupyter notebooks, you simply type a mathematical expression in a code cell, press Shift+Enter, and the result appears right below. Let’s explore the basic mathematical operators that Python provides:

1.1.1 Basic Python Math Operators#

Operator

Description

Example

Result

+

Addition

5 + 3

8

-

Subtraction

5 - 3

2

*

Multiplication

5 * 3

15

/

Division (floating point)

7 / 2

3.5

//

Floor division (integer)

7 // 2

3

%

Modulus (remainder)

7 % 3

1

**

Exponentiation (power)

2 ** 3

8

()

Parentheses (grouping)

(2 + 3) * 4

20

Let’s try some of these operators with real examples:

Python uses the standard math operators for addition, subtraction, multiplication, and division, and follows the usual order of operations. You can use parentheses to control how calculations are evaluated. Python ignores spaces within a line (so you can format expressions for readability), but it does care about spaces at the beginning of a line—an important concept we’ll discuss later when we cover conditions and loops.

# Basic arithmetic
print(f"5 + 3 = {5 + 3}")
print(f"5 * 3 = {5 * 3}")
print(f"5 / 3 = {5 / 3}")
print(f"5 ** 3 (5 to the power of 3) = {5 ** 3}")
5 + 3 = 8
5 * 3 = 15
5 / 3 = 1.6666666666666667
5 ** 3 (5 to the power of 3) = 125

Two operators that often confuse beginners are floor division and modulo. Let’s understand what they do:

Floor Division (//):

  • Returns the whole number part of division (rounds down to nearest integer)

  • Example: 7 // 2 = 3 (not 3.5)

  • Useful when you need to know “how many complete groups” you can make

Modulo (%):

  • Returns the remainder after division

  • Example: 7 % 2 = 1 (because 7 ÷ 2 = 3 remainder 1)

  • Useful for finding what’s “left over” after dividing

print(f"25 // 7 = {25 // 7}")
print(f"25 % 7 = {25 % 7}")
25 // 7 = 3
25 % 7 = 4

1.2 Integers & Floats#

What Are Data Types?

A data type tells Python what kind of information a value represents and what operations can be performed on it. Think of it like categories that help Python understand how to handle different kinds of data. Just like you wouldn’t try to multiply a word by a number in real life, Python uses types to keep track of what makes sense to do with each piece of data.

1.2.1 Two Main Number Types in Python#

Python has two primary types for numbers:

  1. Integers (int)

  • Whole numbers with no decimal point

  • Examples: 2, 53, -10, 0

  • Used for counting, indexing, exact quantities

  • Perfect for things like: number of reactors, batch count, sample size

  1. Floats (float)

  • Numbers with decimal points (even if the decimal part is zero)

  • Examples: 3.0, 1.2, -5.7, 2.5e-3 (scientific notation)

  • Used for measurements, calculations, continuous values

  • Perfect for things like: temperature, pressure, concentration, flow rates

How Python Decides the Result Type

Python follows simple rules when doing math:

  1. All integers + whole number result → Integer

    • 5 + 3 = 8 (integer)

    • 10 * 4 = 40 (integer)

  2. Any float involved → Float

    • 5.0 + 3 = 8.0 (float, because 5.0 is a float)

    • 2 * 3.5 = 7.0 (float, because 3.5 is a float)

  3. Division always produces floats (even with integers)

    • 6 / 2 = 3.0 (float, even though result is whole)

    • 7 / 2 = 3.5 (float)

# Integers vs Floats Examples

print("=== Integer Examples ===")
batch_count = 5           # Integer: whole number of batches
reactor_num = 2           # Integer: reactor identifier
print(f"batch_count = {batch_count} (type: {type(batch_count).__name__})")
print(f"reactor_num = {reactor_num} (type: {type(reactor_num).__name__})")

print("\n=== Float Examples ===")
temperature = 85.7        # Float: temperature measurement
pressure = 2.0            # Float: even though it's whole, it has decimal
flow_rate = 1.25e2        # Float: scientific notation = 125.0
print(f"temperature = {temperature} (type: {type(temperature).__name__})")
print(f"pressure = {pressure} (type: {type(pressure).__name__})")
print(f"flow_rate = {flow_rate} (type: {type(flow_rate).__name__})")

print("\n=== How Python Decides Result Types ===")

# Integer operations
result1 = 10 + 5          # Both integers → integer result
print(f"10 + 5 = {result1} (type: {type(result1).__name__})")

# Mixed operations (float involved)
result2 = 10 + 5.0        # One float → float result
print(f"10 + 5.0 = {result2} (type: {type(result2).__name__})")

result3 = 2 * 3.5         # One float → float result
print(f"2 * 3.5 = {result3} (type: {type(result3).__name__})")

# Division always gives float
result4 = 6 / 2           # Division → always float
print(f"6 / 2 = {result4} (type: {type(result4).__name__})")

result5 = 7 / 2           # Division → always float
print(f"7 / 2 = {result5} (type: {type(result5).__name__})")

# Floor division gives integer if inputs are integers
result6 = 7 // 2          # Floor division with integers → integer
print(f"7 // 2 = {result6} (type: {type(result6).__name__})")
=== Integer Examples ===
batch_count = 5 (type: int)
reactor_num = 2 (type: int)

=== Float Examples ===
temperature = 85.7 (type: float)
pressure = 2.0 (type: float)
flow_rate = 125.0 (type: float)

=== How Python Decides Result Types ===
10 + 5 = 15 (type: int)
10 + 5.0 = 15.0 (type: float)
2 * 3.5 = 7.0 (type: float)
6 / 2 = 3.0 (type: float)
7 / 2 = 3.5 (type: float)
7 // 2 = 3 (type: int)

Why Data Types Matter

In Python (and scientific computing), the data type you choose affects:

  1. How much memory your data uses

  2. How precise your numerical calculations are

Choosing the right data type helps you balance accuracy, performance, and memory efficiency, especially for large datasets or simulations.


Example: float32 vs. float64

Two common numeric types are:

  • float32 — 32-bit floating-point number

  • float64 — 64-bit floating-point number (NumPy’s default)

Memory Usage

Each type uses a fixed amount of memory per number:

Data Type

Size per Value

Memory for 1 Million Values

float32

4 bytes

~4 MB

float64

8 bytes

~8 MB

float64 uses twice the memory of float32. For large simulations or datasets, this difference becomes significant.


Accuracy

float64 stores more bits, giving more precise numerical results.

  • float32 precision: ~7 decimal digits

  • float64 precision: ~15–16 decimal digits

# Why do float32 and float64 give different results?

import numpy as np

x32 = np.float32(0.1)
x64 = np.float64(0.1)

print("=== The Results ===")
print(f"float32: {x32} × 3 = {x32 * 3}")
print(f"float64: {x64} × 3 = {x64 * 3}")

print("\n=== Why Are They Different? ===")
print("The issue is that 0.1 cannot be represented exactly in binary!")
print("Just like 1/3 = 0.333... in decimal (goes on forever),")
print("0.1 in binary is 0.000110011001100... (repeating pattern)")

print(f"\n=== How Each Type Stores 0.1 ===")
print(f"float32 stores 0.1 as: {x32:.17f}")
print(f"float64 stores 0.1 as: {x64:.17f}")
print(f"True decimal 0.1:      0.10000000000000000")

print(f"\n=== The Multiplication Results ===")
result32 = x32 * 3
result64 = x64 * 3
print(f"float32: {x32:.17f} × 3 = {result32:.17f}")
print(f"float64: {x64:.17f} × 3 = {result64:.17f}")
print(f"Expected: 0.3 exactly")

print(f"\n=== Error Analysis ===")
expected = 0.3
error32 = abs(result32 - expected)
error64 = abs(result64 - expected)
print(f"float32 error: {error32:.2e}")
print(f"float64 error: {error64:.2e}")
print(f"float64 is {error32/error64:.1f}x more accurate!")
=== The Results ===
float32: 0.10000000149011612 × 3 = 0.30000000447034836
float64: 0.1 × 3 = 0.30000000000000004

=== Why Are They Different? ===
The issue is that 0.1 cannot be represented exactly in binary!
Just like 1/3 = 0.333... in decimal (goes on forever),
0.1 in binary is 0.000110011001100... (repeating pattern)

=== How Each Type Stores 0.1 ===
float32 stores 0.1 as: 0.10000000149011612
float64 stores 0.1 as: 0.10000000000000001
True decimal 0.1:      0.10000000000000000

=== The Multiplication Results ===
float32: 0.10000000149011612 × 3 = 0.30000000447034836
float64: 0.10000000000000001 × 3 = 0.30000000000000004
Expected: 0.3 exactly

=== Error Analysis ===
float32 error: 4.47e-09
float64 error: 5.55e-17
float64 is 80530637.0x more accurate!

💡 Key Takeaway

  • Computers can’t store all decimal numbers exactly.

  • float64 is more precise than float32.

  • For most engineering work, these tiny errors don’t matter.

  • But in long calculations, small errors can add up!

1.3 Python Functions#

In addition to basic mathematical operators, Python contains a number of built-in functions. As in mathematics, a function has a name (e.g., abs() or round()) and the arguments are placed inside parentheses after the name. An argument is any value or piece of information fed into a function.

For example, abs(-5) requires a single argument (the number -5) and returns its absolute value.

def round_custom(value):
    integer_part = int(value)
    frac_part = value - integer_part

    if frac_part >= 0.5:
        answer = integer_part + 1
    else:
        answer = integer_part
    return answer

input_value = 3.9
print(round(input_value))
print(round_custom(input_value))
4
4

1.3.1 Common Python Math Functions#

Function

Description

Example

Result

abs(x)

Absolute value

abs(-7.5)

7.5

round(x)

Round to nearest integer

round(3.7)

4

round(x, n)

Round to n decimal places

round(3.14159, 2)

3.14

max(a, b, ...)

Maximum value

max(5, 2, 9)

9

min(a, b, ...)

Minimum value

min(5, 2, 9)

2

pow(x, y)

x to the power of y

pow(2, 3)

8

len(x)

Length of sequence

len([1, 2, 3])

3

Important Note: The round() function uses “Banker’s rounding” (also called “round half to even”). If a number is exactly halfway between two integers (e.g., 4.5), it rounds toward the even integer. So round(4.5) = 4 and round(5.5) = 6.

Let’s try some examples:

round(4.5)
4
# Python Built-in Math Functions Examples

print("=== Absolute Value Function ===")
temperature_error = -2.5  # °C
print(f"Temperature error: {temperature_error}°C")
print(f"Absolute error: {abs(temperature_error)}°C")

print("\n=== Rounding Functions ===")
measurement = 15.6789  # some measured value
print(f"Original measurement: {measurement}")
print(f"Rounded to integer: {round(measurement)}")
print(f"Rounded to 2 decimal places: {round(measurement, 2)}")
print(f"Rounded to 1 decimal place: {round(measurement, 1)}")

print("\n=== Banker's Rounding Examples ===")
print(f"round(4.5) = {round(4.5)} (rounds to even)")
print(f"round(5.5) = {round(5.5)} (rounds to even)")
print(f"round(3.5) = {round(3.5)} (rounds to even)")
print(f"round(6.5) = {round(6.5)} (rounds to even)")

print("\n=== Min/Max Functions ===")
reactor_temps = [78.5, 82.1, 79.3, 85.7, 77.9]  # °C
print(f"Reactor temperatures: {reactor_temps}")
print(f"Minimum temperature: {min(reactor_temps)}°C")
print(f"Maximum temperature: {max(reactor_temps)}°C")
print(f"Temperature range: {max(reactor_temps) - min(reactor_temps):.1f}°C")

print("\n=== Power and Length Functions ===")
print(f"pow(2, 3) = {pow(2, 3)} (same as 2**3 = {2**3})")
print(f"Length of temperature list: {len(reactor_temps)} measurements")
=== Absolute Value Function ===
Temperature error: -2.5°C
Absolute error: 2.5°C

=== Rounding Functions ===
Original measurement: 15.6789
Rounded to integer: 16
Rounded to 2 decimal places: 15.68
Rounded to 1 decimal place: 15.7

=== Banker's Rounding Examples ===
round(4.5) = 4 (rounds to even)
round(5.5) = 6 (rounds to even)
round(3.5) = 4 (rounds to even)
round(6.5) = 6 (rounds to even)

=== Min/Max Functions ===
Reactor temperatures: [78.5, 82.1, 79.3, 85.7, 77.9]
Minimum temperature: 77.9°C
Maximum temperature: 85.7°C
Temperature range: 7.8°C

=== Power and Length Functions ===
pow(2, 3) = 8 (same as 2**3 = 8)
Length of temperature list: 5 measurements

Other than mathematical operation Python has basic functions,l ike print(), open(), type(), etc

1.4 Variables#

Variables are like labeled containers that store values in your computer’s memory. Think of them as storage boxes with names written on them - you can put different things inside and refer to them by their labels.

A variable has three key components:

  1. Name - What you call it (e.g., temperature, pressure)

  2. Value - What’s stored inside (e.g., 85.5, "distillation")

  3. Type - What kind of data it is (e.g., number, text)

a = 1
print(a)

a = 2
print(a)
1
2
variable = 2

print('addition')
print(variable + 100)

print('subtraction')
print(variable -100)

print('multiplication')
print(variable *100)
addition
102
subtraction
-98
multiplication
200

1.4.1 Creating & Assigning Variables#

In Python, you create a variable by simply assigning a value to a name using the = sign:

temperature = 85.5          # Creates a variable named 'temperature'
process_name = "reactor"    # Creates a variable named 'process_name'
sample_count = 10           # Creates a variable named 'sample_count'

Variable Naming Rules:

Required Rules (Python will give an error if broken):

  • Must start with a letter (a-z, A-Z) or underscore (_)

  • Can contain letters, numbers, and underscores

  • Cannot contain spaces or special characters (@, #, $, etc.)

  • Cannot be Python keywords (like print, if, for)

Best Practices (for readable code):

  • Use descriptive names: reactor_temperature not temp

  • Use lowercase with underscores: flow_rate not FlowRate

  • Avoid confusing names: pressure_1 and pressure_2 instead of p1 and p2

Abc = 123
abc = 123
_abc = 123
1abc
  Cell In[11], line 1
    1abc
    ^
SyntaxError: invalid decimal literal
#print('hello world')

# print = 123
# print
123
print('hello world')
hello world
a = 1
print(a)

b = 'hello world'
print(b)
1
hello world
variable = 1000
print(variable + 2)
print(variable - 2)
print(variable * 2)
print(variable / 2)
1002
998
2000
500.0
if = 123
  Cell In[2], line 1
    if = 123
       ^
SyntaxError: invalid syntax

Python Reserved Words (Keywords):

These words have special meanings in Python and cannot be used as variable names. Python will give you an error if you try to use them:

Reserved Word

Purpose

Example Usage

and

Logical AND operator

if temp > 80 and pressure < 5:

or

Logical OR operator

if status == "error" or count == 0:

not

Logical NOT operator

if not is_running:

if

Conditional statement

if temperature > 100:

elif

Else-if in conditional

elif pressure > 5:

else

Else in conditional

else: print("Normal")

for

For loop

for i in range(10):

while

While loop

while temp < 80:

in

Membership test

if "error" in status:

is

Identity test

if result is None:

def

Define function

def calculate_pressure():

class

Define class

class Reactor:

return

Return from function

return pressure * volume

import

Import module

import numpy as np

from

Import specific items

from math import pi

as

Create alias

import matplotlib as plt

try

Try block (error handling)

try: result = 10/0

except

Except block

except ZeroDivisionError:

finally

Finally block

finally: cleanup()

with

Context manager

with open("data.txt") as f:

lambda

Anonymous function

square = lambda x: x**2

True

Boolean true value

is_running = True

False

Boolean false value

is_complete = False

None

Null value

result = None

Common Mistakes to Avoid:

# ❌ These will cause errors:
if = 85.5        # 'if' is reserved
for = "reactor"  # 'for' is reserved  
class = 10       # 'class' is reserved

# ✅ Use these instead:
condition = 85.5
process = "reactor"
category = 10

Pro Tip: If you accidentally use a reserved word, Python will show an error like:

SyntaxError: invalid syntax

Choose descriptive variable names that don’t conflict with these keywords!

Why Variables Matter in Engineering:

  • Store measurements: pressure = 2.5, temperature = 85.0

  • Track calculations: efficiency = output / input * 100

  • Label data: process_name = "distillation"

  • Reuse values: Calculate once, use many times

  • Make code readable: reactor_volume is clearer than just 10

Let’s see variables in action:

1.4.2 Compound Assignment#

Compound assignment operators are shortcuts that combine an operation with variable assignment. Instead of writing temperature = temperature + 5, you can write temperature += 5. These operators make your code more concise and are commonly used in engineering calculations.

Compound Assignment Operators

Operator

Equivalent

Description

Example

Result

+=

x = x + y

Add and assign

temp += 10

Increase temp by 10

-=

x = x - y

Subtract and assign

pressure -= 0.5

Decrease pressure by 0.5

*=

x = x * y

Multiply and assign

volume *= 2

Double the volume

/=

x = x / y

Divide and assign

concentration /= 2

Halve concentration

//=

x = x // y

Floor divide and assign

count //= 3

Integer division

%=

x = x % y

Modulo and assign

remainder %= 10

Get remainder

**=

x = x ** y

Power and assign

area **= 0.5

Square root of area

Why Use Compound Assignment?

  1. Shorter code: count += 1 vs count = count + 1

  2. Less typing: Fewer chances for typos

  3. Clearer intent: Shows you’re modifying the existing variable

  4. Common pattern: Very frequently used in loops and iterative calculations

temperature = 100 # Celcius
print(temperature)
temperature = temperature + 10
print(temperature)


temperature = 100 # Celcius
print(temperature)
temperature += 10
print(temperature)
100
110
100
110
a = 0
for i in [1,2,3,4,5]:
    a += i
    print(a)
1
3
6
10
15
# Compound Assignment Operators Examples

print("=== Basic Compound Assignment Examples ===")

# Starting values
temperature = 75.0  # °C
pressure = 2.0      # bar
volume = 100        # L
batch_count = 0

print(f"Initial values:")
print(f"Temperature: {temperature}°C")
print(f"Pressure: {pressure} bar")
print(f"Volume: {volume} L")
print(f"Batch count: {batch_count}")

print("\n=== Addition Assignment (+=) ===")
temperature += 15.5  # Same as: temperature = temperature + 15.5
batch_count += 1     # Increment batch counter
print(f"After heating: temperature = {temperature}°C")
print(f"After processing: batch_count = {batch_count}")

print("\n=== Subtraction Assignment (-=) ===")
pressure -= 0.3      # Pressure drop
volume -= 25         # Volume consumed
print(f"After pressure drop: pressure = {pressure} bar")
print(f"After consumption: volume = {volume} L")

print("\n=== Multiplication Assignment (*=) ===")
# Scale up production
production_rate = 50  # kg/hr
production_rate *= 1.5  # Increase by 50%
print(f"Scaled production rate: {production_rate} kg/hr")

print("\n=== Division Assignment (/=) ===")
concentration = 25.0  # mol/L
concentration /= 2    # Dilute by half
print(f"After dilution: concentration = {concentration} mol/L")

print("\n=== Power Assignment (**=) ===")
# Calculate area from diameter
diameter = 4.0  # m
radius = diameter / 2
area = radius  # Start with radius
area **= 2     # Square it (r²)
area *= 3.14159  # Multiply by π
print(f"Circle area (d={diameter}m): {area:.2f} m²")
=== Basic Compound Assignment Examples ===
Initial values:
Temperature: 75.0°C
Pressure: 2.0 bar
Volume: 100 L
Batch count: 0

=== Addition Assignment (+=) ===
After heating: temperature = 90.5°C
After processing: batch_count = 1

=== Subtraction Assignment (-=) ===
After pressure drop: pressure = 1.7 bar
After consumption: volume = 75 L

=== Multiplication Assignment (*=) ===
Scaled production rate: 75.0 kg/hr

=== Division Assignment (/=) ===
After dilution: concentration = 12.5 mol/L

=== Power Assignment (**=) ===
Circle area (d=4.0m): 12.57 m²