Visualizing a Möbius Strip

Problem Description

Topology studies properties of shapes that are preserved under continuous deformations.

One famous example is the Möbius strip, a surface with only one side and one edge.

We will:

  1. Generate and visualize a Möbius strip using $Python$.
  2. Understand its unique topological properties.

Python Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# Function to generate a Möbius strip
def generate_mobius_strip(u_samples=100, v_samples=50):
"""
Generate the x, y, z coordinates of a Möbius strip.

Parameters:
u_samples: Number of samples along the strip (angle direction)
v_samples: Number of samples across the strip (width direction)

Returns:
x, y, z: Coordinates of the Möbius strip
"""
# Create parameter ranges for u (angle) and v (width)
u = np.linspace(0, 2 * np.pi, u_samples)
v = np.linspace(-0.5, 0.5, v_samples)
u, v = np.meshgrid(u, v)

# Parametric equations for a Möbius strip
x = (1 + v * np.cos(u / 2)) * np.cos(u)
y = (1 + v * np.cos(u / 2)) * np.sin(u)
z = v * np.sin(u / 2)

return x, y, z

# Generate Möbius strip data
x, y, z = generate_mobius_strip()

# Plot the Möbius strip
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')

# Surface plot with color gradient
ax.plot_surface(x, y, z, cmap='viridis', edgecolor='k', alpha=0.9)

# Set plot aesthetics
ax.set_title("Möbius Strip", fontsize=16)
ax.set_xlabel("X-axis")
ax.set_ylabel("Y-axis")
ax.set_zlabel("Z-axis")
plt.show()

Explanation of the Code

  1. Parameterization:

    • The Möbius strip is represented parametrically:
      $$
      x = (1 + v \cdot \cos(u / 2)) \cdot \cos(u)
      $$
      $$
      y = (1 + v \cdot \cos(u / 2)) \cdot \sin(u)
      $$
      $$
      z = v \cdot \sin(u / 2)
      $$
    • $( u )$ ranges from $0$ to $( 2\pi )$, covering the circular path.
    • $( v )$ represents the “width” of the strip and varies symmetrically around the center.
  2. Meshgrid:

    • $( u )$ and $( v )$ are used to create a grid of points for generating the 3D surface.
  3. Plotting:

    • plot_surface is used to visualize the Möbius strip.
    • The viridis colormap adds depth and clarity to the surface, making the twisted structure easier to understand.

Results

  1. Graph:

    • The Möbius strip is displayed as a 3D surface.
    • The twist in the strip highlights its unique topology, with only one side and one edge.
  2. Topological Properties:

    • Single Surface: If you travel along the Möbius strip, you’ll return to your starting point having traversed both “sides,” demonstrating there is only one continuous surface.
    • Single Edge: Unlike a typical loop, the Möbius strip has only one edge.
  3. Insights:

    • This visualization showcases the fundamental concept of non-orientability in topology.
    • It provides an intuitive understanding of how shapes can behave in higher dimensions or with non-standard geometries.

Conclusion

The Möbius strip is a simple yet profound example of a topological structure.

By visualizing it in $Python$, we can gain insights into its properties and how topology can challenge our traditional understanding of geometry.

Finding the Circumcircle of a Triangle

Problem Description

In $Euclidean$ $geometry$, the circumcircle of a triangle is the unique circle that passes through all three vertices of the triangle.

Given three points $(A, B, C)$ in a 2D plane, we want to:

  1. Calculate the circumcenter (center of the circumcircle) and its radius.
  2. Plot the triangle and its circumcircle.

Python Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import numpy as np
import matplotlib.pyplot as plt

# Function to calculate the circumcenter and radius
def find_circumcircle(A, B, C):
# Coordinates of the points
x1, y1 = A
x2, y2 = B
x3, y3 = C

# Calculate the slopes of the perpendicular bisectors
D = 2 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2))
if D == 0:
raise ValueError("Points are collinear; circumcircle cannot be determined.")

# Circumcenter coordinates
Ux = ((x1**2 + y1**2) * (y2 - y3) + (x2**2 + y2**2) * (y3 - y1) + (x3**2 + y3**2) * (y1 - y2)) / D
Uy = ((x1**2 + y1**2) * (x3 - x2) + (x2**2 + y2**2) * (x1 - x3) + (x3**2 + y3**2) * (x2 - x1)) / D

# Radius of the circumcircle
radius = np.sqrt((x1 - Ux)**2 + (y1 - Uy)**2)

return (Ux, Uy), radius

# Function to plot the triangle and its circumcircle
def plot_triangle_and_circumcircle(A, B, C):
# Calculate circumcenter and radius
circumcenter, radius = find_circumcircle(A, B, C)

# Create the circle
circle = plt.Circle(circumcenter, radius, color='blue', fill=False, linestyle='--', label='Circumcircle')

# Plot the triangle
x_coords = [A[0], B[0], C[0], A[0]]
y_coords = [A[1], B[1], C[1], A[1]]

plt.figure(figsize=(8, 8))
plt.plot(x_coords, y_coords, '-o', label='Triangle', color='red')
plt.scatter(*circumcenter, color='green', label='Circumcenter')
plt.gca().add_artist(circle)

# Formatting
plt.title("Triangle and its Circumcircle")
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
plt.axis('equal')
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend()
plt.show()

# Example points
A = (1, 1)
B = (7, 1)
C = (4, 5)

# Plot the triangle and circumcircle
plot_triangle_and_circumcircle(A, B, C)

Explanation of the Code

  1. Circumcenter Calculation:

    • The circumcenter is calculated using the perpendicular bisectors of the triangle’s sides.
      The formula is derived from solving linear equations for perpendicular bisectors.
  2. Radius:

    • The radius is the distance from the circumcenter to any vertex of the triangle.
  3. Plotting:

    • The triangle is plotted using the given vertices.
    • The circumcircle is drawn using the calculated center and radius.
    • The circumcenter is marked as a green dot for clarity.

Visualization

  • Red Triangle: Represents the triangle formed by the points $( A, B, C )$.
  • Blue Dashed Circle: Represents the circumcircle, passing through all three vertices.
  • Green Dot: Marks the circumcenter of the triangle.

Example Results

For the given points:

  • $ A = (1, 1), B = (7, 1), C = (4, 5) $,
  • The circumcenter is approximately $ (4.0, 2.8) $,
  • The radius is approximately $ 3.6 $.

The graph illustrates how the circumcircle perfectly encapsulates the triangle, demonstrating the geometric property.

Sorting Algorithm Performance

Problem Description

We will analyze the performance of three popular sorting algorithms:

  1. Bubble Sort (Simple but slow for large datasets),
  2. Merge Sort (Efficient divide-and-conquer algorithm),
  3. Python’s Built-in Timsort (Used by Python’s sorted() function).

The goal is to:

  1. Measure the time taken by each algorithm to sort a list of random numbers of increasing size.
  2. Visualize the time complexity and compare their performance.

Python Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import time
import random
import matplotlib.pyplot as plt

# Step 1: Implement sorting algorithms

# Bubble Sort
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]

# Merge Sort
def merge_sort(arr):
if len(arr) > 1:
mid = len(arr) // 2
L = arr[:mid]
R = arr[mid:]

merge_sort(L)
merge_sort(R)

i = j = k = 0

while i < len(L) and j < len(R):
if L[i] < R[j]:
arr[k] = L[i]
i += 1
else:
arr[k] = R[j]
j += 1
k += 1

while i < len(L):
arr[k] = L[i]
i += 1
k += 1

while j < len(R):
arr[k] = R[j]
j += 1
k += 1

# Step 2: Test and time the algorithms
def test_sorting_algorithms():
sizes = [100, 500, 1000, 5000, 10000] # List sizes to test
bubble_times = []
merge_times = []
timsort_times = []

for size in sizes:
arr = [random.randint(0, 100000) for _ in range(size)]

# Bubble Sort
arr_copy = arr[:]
start_time = time.time()
bubble_sort(arr_copy)
bubble_times.append(time.time() - start_time)

# Merge Sort
arr_copy = arr[:]
start_time = time.time()
merge_sort(arr_copy)
merge_times.append(time.time() - start_time)

# Timsort (Python's built-in sorted())
arr_copy = arr[:]
start_time = time.time()
sorted(arr_copy)
timsort_times.append(time.time() - start_time)

return sizes, bubble_times, merge_times, timsort_times

# Step 3: Run the test
sizes, bubble_times, merge_times, timsort_times = test_sorting_algorithms()

# Step 4: Visualize the results
plt.figure(figsize=(10, 6))
plt.plot(sizes, bubble_times, label="Bubble Sort", marker='o', color='red')
plt.plot(sizes, merge_times, label="Merge Sort", marker='o', color='blue')
plt.plot(sizes, timsort_times, label="Timsort", marker='o', color='green')
plt.title("Sorting Algorithm Performance")
plt.xlabel("List Size")
plt.ylabel("Time Taken (seconds)")
plt.yscale("log") # Use logarithmic scale for better comparison
plt.grid(True, linestyle="--", alpha=0.6)
plt.legend()
plt.show()

Explanation of the Code

  1. Sorting Algorithms:

    • Bubble Sort:
      Compares adjacent elements and swaps them if they are in the wrong order.
      Very slow for large datasets $ O(n^2) $.
    • Merge Sort:
      Recursively divides the array into halves, sorts each half, and then merges them $ O(n \log n) $.
    • Timsort:
      Python’s optimized built-in algorithm, designed for real-world performance $ O(n \log n) $.
  2. Performance Measurement:

    • The algorithms are tested on arrays of increasing size: $100$, $500$, $1000$, $5000$, and $10,000$.
    • Execution time for each algorithm is measured using Python’s time module.
  3. Visualization:

    • The results are plotted with the list size on the $x$-axis and execution time on the $y$-axis.
    • A logarithmic scale is used for the $y$-axis to highlight differences between the algorithms.

Graph Explanation

  • X-axis:
    Represents the size of the list.
  • Y-axis:
    Represents the time taken (in seconds) on a logarithmic scale.
  • Observations:
    • Bubble Sort:
      The performance deteriorates rapidly as the list size increases, due to its quadratic time complexity $ O(n^2) $.
    • Merge Sort:
      Performs significantly better for large lists, maintaining $ O(n \log n) $ complexity.
    • Timsort:
      Outperforms both Bubble Sort and Merge Sort, as it is optimized for typical sorting use cases.

This example illustrates how algorithmic choices impact performance and the importance of selecting efficient algorithms for large datasets.

The Halting Problem (Illustration via the Collatz Conjecture)

Problem

The Halting Problem is a famous problem in computability theory, which states that no algorithm can universally determine whether an arbitrary program will halt or run forever.

To illustrate this concept, we’ll use the Collatz Conjecture, which is a sequence defined as follows:

  1. Start with any positive integer $( n )$.
  2. If $( n )$ is even, divide it by $2$.
  3. If $( n )$ is odd, multiply it by $3$ and add $1$.
  4. Repeat until $( n = 1 )$.

The conjecture states that this sequence will always reach $1$, no matter the starting $( n )$.

However, this has not been proven for all integers.

We’ll implement this in $Python$, simulate the process for various starting values of $( n )$, and visualize the results.


Python Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import matplotlib.pyplot as plt

# Function to compute the Collatz sequence
def collatz_sequence(n):
steps = 0
sequence = [n]
while n != 1:
if n % 2 == 0:
n = n // 2
else:
n = 3 * n + 1
sequence.append(n)
steps += 1
return sequence, steps

# Simulate Collatz for multiple starting numbers
start_numbers = range(1, 51) # Starting numbers from 1 to 50
step_counts = [] # To store the number of steps for each starting number

# Compute the sequence for each starting number
for start in start_numbers:
_, steps = collatz_sequence(start)
step_counts.append(steps)

# Visualization
plt.figure(figsize=(12, 6))

# Plot the number of steps for each starting number
plt.bar(start_numbers, step_counts, color="skyblue", edgecolor="black")
plt.title("Collatz Conjecture: Number of Steps to Reach 1")
plt.xlabel("Starting Number")
plt.ylabel("Number of Steps")
plt.grid(axis="y", linestyle="--", alpha=0.7)
plt.show()

# Example sequence for a specific number
n_example = 27
sequence, _ = collatz_sequence(n_example)

# Plot the sequence for the example starting number
plt.figure(figsize=(12, 6))
plt.plot(range(len(sequence)), sequence, marker="o", color="red", label=f"Starting Number: {n_example}")
plt.title(f"Collatz Sequence for Starting Number {n_example}")
plt.xlabel("Step")
plt.ylabel("Value")
plt.grid(True, linestyle="--", alpha=0.7)
plt.legend()
plt.show()

Explanation

  1. Collatz Sequence:

    • The function collatz_sequence(n) generates the sequence for a given starting number $( n )$ and counts the steps until it reaches $1$.
  2. Simulation:

    • For each starting number from $1$ to $50$, the sequence is computed, and the number of steps is recorded.
  3. Visualization:

    • Steps to Reach 1:
      • A bar plot shows the number of steps needed for each starting number to reach $1$.
      • Some numbers, like $27$, require significantly more steps, illustrating the unpredictable nature of the sequence.
    • Example Sequence:
      • The Collatz sequence for $( n = 27 )$ is plotted, showing its behavior before eventually reaching $1$.

Output

  1. Bar Plot:

    • The number of steps varies widely between starting numbers, demonstrating the complexity of predicting halting behavior.
  2. Sequence Plot:

    • For $( n = 27 )$, the sequence has a chaotic rise and fall before stabilizing at $1$.

This example illustrates the undecidability aspect of the Halting Problem using the unpredictable behavior of the Collatz Conjecture.

While we can compute results for specific inputs, proving general behavior (e.g., all sequences halt) is non-trivial, aligning with the essence of computability theory.

Random Walk and Ornstein-Uhlenbeck Processes

I’ll create an example using a stochastic process - let’s simulate and analyze a Random Walk with drift and an Ornstein-Uhlenbeck process, which is often used in financial modeling.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import numpy as np
import matplotlib.pyplot as plt

# Set random seed for reproducibility
np.random.seed(42)

def simulate_random_walk(n_steps, drift=0.1, volatility=1.0, paths=5):
"""
Simulate random walk with drift
Parameters:
- n_steps: number of time steps
- drift: trend component
- volatility: size of random movements
- paths: number of paths to simulate
"""
steps = np.random.normal(drift, volatility, (paths, n_steps))
paths = np.cumsum(steps, axis=1)
return paths

def simulate_ornstein_uhlenbeck(n_steps, theta=1.0, mu=0.0, sigma=0.5, paths=5):
"""
Simulate Ornstein-Uhlenbeck process
Parameters:
- n_steps: number of time steps
- theta: mean reversion rate
- mu: long-term mean
- sigma: volatility
- paths: number of paths to simulate
"""
dt = 0.01
t = np.linspace(0, n_steps*dt, n_steps)
paths_ou = np.zeros((paths, n_steps))

for i in range(paths):
x = np.zeros(n_steps)
x[0] = mu
for j in range(1, n_steps):
dx = theta * (mu - x[j-1]) * dt + sigma * np.sqrt(dt) * np.random.normal()
x[j] = x[j-1] + dx
paths_ou[i] = x

return t, paths_ou

# Simulation parameters
n_steps = 1000
n_paths = 5

# Simulate processes
rw_paths = simulate_random_walk(n_steps, drift=0.1, volatility=0.5, paths=n_paths)
t_ou, ou_paths = simulate_ornstein_uhlenbeck(n_steps, theta=2.0, mu=0.0, sigma=1.0, paths=n_paths)

# Create visualizations
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))

# Plot Random Walk paths
time = np.arange(n_steps)
for i in range(n_paths):
ax1.plot(time, rw_paths[i], label=f'Path {i+1}')
ax1.set_title('Random Walk with Drift')
ax1.set_xlabel('Time Steps')
ax1.set_ylabel('Value')
ax1.grid(True, alpha=0.3)
ax1.legend()

# Add theoretical drift line
drift_line = 0.1 * time
ax1.plot(time, drift_line, '--', color='black', label='Theoretical Drift', alpha=0.5)

# Plot Ornstein-Uhlenbeck paths
for i in range(n_paths):
ax2.plot(t_ou, ou_paths[i], label=f'Path {i+1}')
ax2.axhline(y=0, color='black', linestyle='--', label='Long-term Mean', alpha=0.5)
ax2.set_title('Ornstein-Uhlenbeck Process')
ax2.set_xlabel('Time')
ax2.set_ylabel('Value')
ax2.grid(True, alpha=0.3)
ax2.legend()

plt.tight_layout()
plt.show()

# Calculate and print statistics
print("Random Walk Statistics:")
print(f"Final values: {rw_paths[:, -1]}")
print(f"Mean final value: {np.mean(rw_paths[:, -1]):.3f}")
print(f"Std of final values: {np.std(rw_paths[:, -1]):.3f}")

print("\nOrnstein-Uhlenbeck Statistics:")
print(f"Final values: {ou_paths[:, -1]}")
print(f"Mean final value: {np.mean(ou_paths[:, -1]):.3f}")
print(f"Std of final values: {np.std(ou_paths[:, -1]):.3f}")

Let me explain this example which demonstrates two different types of stochastic processes:

  1. Random Walk with Drift:

    • A discrete-time process where each step includes:
      • A constant drift term (trend)
      • A random component (noise)
    • Properties:
      • Non-stationary process
      • Variance increases with time
      • Used in financial modeling for asset prices
  2. Ornstein-Uhlenbeck Process:

    • A continuous-time mean-reverting process
    • Properties:
      • Mean-reverting (tends to return to a long-term average)
      • Stationary process
      • Often used to model interest rates or volatility

Key Components of the Code:

  1. simulate_random_walk:

    • Adds drift to random normal steps
    • Uses cumulative sum to create the path
    • Multiple paths show variation in possible outcomes
  2. simulate_ornstein_uhlenbeck:

    • Implements the OU stochastic differential equation
    • Mean reversion rate ($theta$) controls speed of return to mean
    • $Sigma$ controls volatility of the process

Visualization Explanation:

Random Walk Statistics:
Final values: [109.66602791 135.41811862 102.91710728  90.64039111  75.3631803 ]
Mean final value: 102.801
Std of final values: 20.059

Ornstein-Uhlenbeck Statistics:
Final values: [ 0.57734044  0.33628409  0.89794221  0.3854573  -0.2574374 ]
Mean final value: 0.388
Std of final values: 0.378

Top Graph (Random Walk):

  • Shows $5$ sample paths with positive drift
  • Black dashed line shows theoretical drift
  • Paths tend to move upward due to positive drift
  • Increasing spread over time shows growing uncertainty

Bottom Graph (Ornstein-Uhlenbeck):

  • Shows $5$ sample paths
  • Black dashed line shows long-term mean ($0$)
  • Paths constantly revert toward the mean
  • Consistent spread shows stationary behavior

Key Differences Between the Processes:

  1. The Random Walk:

    • Tends to drift away from starting point
    • Variance increases with time
    • No mean reversion
  2. The Ornstein-Uhlenbeck Process:

    • Stays around the mean
    • Has constant variance over time
    • Shows strong mean reversion

This example demonstrates important concepts in stochastic processes:

  • Mean reversion vs random drift
  • Stationary vs non-stationary processes
  • The role of drift and volatility
  • Different types of randomness in financial modeling

Simulating and Visualizing a Normal Distribution in Python

Problem

Suppose we want to simulate a normal distribution $( \mathcal{N}(\mu, \sigma^2) )$ with a mean $( \mu = 50 )$ and a standard deviation $( \sigma = 10 )$.

Using this distribution, we will:

  1. Generate a random sample of size $1000$.
  2. Compute the sample mean and standard deviation to verify they align with the theoretical values.
  3. Visualize the distribution as a histogram overlaid with the theoretical probability density function ($PDF$).

Python Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

# Parameters of the normal distribution
mu = 50 # Mean
sigma = 10 # Standard deviation
sample_size = 1000

# Generate a random sample
np.random.seed(42) # For reproducibility
sample = np.random.normal(mu, sigma, sample_size)

# Compute sample statistics
sample_mean = np.mean(sample)
sample_std = np.std(sample)

print(f"Sample Mean: {sample_mean:.2f}")
print(f"Sample Standard Deviation: {sample_std:.2f}")

# Visualization
x = np.linspace(mu - 4 * sigma, mu + 4 * sigma, 1000) # x-axis range
pdf = norm.pdf(x, mu, sigma) # Theoretical PDF

plt.figure(figsize=(10, 6))

# Plot the histogram of the sample
plt.hist(sample, bins=30, density=True, alpha=0.6, color='skyblue', label="Sample Histogram")

# Overlay the theoretical PDF
plt.plot(x, pdf, color='red', linewidth=2, label="Theoretical PDF")

# Add labels and legend
plt.title("Normal Distribution: Histogram and Theoretical PDF")
plt.xlabel("Value")
plt.ylabel("Density")
plt.axvline(sample_mean, color='green', linestyle='--', label=f"Sample Mean: {sample_mean:.2f}")
plt.axvline(mu, color='purple', linestyle=':', label=f"Theoretical Mean: {mu}")
plt.legend()
plt.grid(True)
plt.show()

Explanation

  1. Random Sample:

    • We generate a sample of size $1000$ from the normal distribution $ \mathcal{N}(\mu=50, \sigma=10) $ using np.random.normal.
  2. Sample Statistics:

    • The mean $( \bar{x} )$ and standard deviation $( s )$ of the sample are computed using np.mean and np.std.
      These values should closely match the theoretical values $( \mu = 50 )$ and $( \sigma = 10 )$ due to the large sample size.
  3. Visualization:

    • The histogram of the sample shows the empirical distribution of the generated data.
    • The theoretical PDF $( \frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{(x-\mu)^2}{2\sigma^2}} )$ is overlaid to illustrate how closely the sample follows the theoretical distribution.

Output

  1. Sample Statistics:

    1
    2
    Sample Mean: 50.19
    Sample Standard Deviation: 9.79

    These values are very close to the theoretical $( \mu = 50 )$ and $( \sigma = 10 )$, verifying the correctness of the simulation.

  2. Graph:

    • The histogram of the sample aligns closely with the red line (theoretical $PDF$).
    • Vertical lines indicate the sample mean (green) and the theoretical mean (purple), showing their proximity.

This example demonstrates how to simulate, analyze, and visualize data from a probability distribution effectively using $Python$.

Numerical Analysis

Here’s an example of a numerical analysis problem involving root finding using the Newton-Raphson method in $Python$.

Problem

Find a root of the function $ f(x) = x^3 - 6x^2 + 11x - 6.1 $ using the Newton-Raphson method.

Start with an initial guess $( x_0 = 3.5 )$, and iterate until the error is less than $( 10^{-6} )$.


Python Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import numpy as np
import matplotlib.pyplot as plt

# Define the function and its derivative
def f(x):
return x**3 - 6*x**2 + 11*x - 6.1

def f_prime(x):
return 3*x**2 - 12*x + 11

# Newton-Raphson Method
def newton_raphson(f, f_prime, x0, tol=1e-6, max_iter=100):
x = x0
iteration = 0
errors = []

for _ in range(max_iter):
# Calculate next point
x_new = x - f(x) / f_prime(x)
error = abs(x_new - x)
errors.append(error)

# Check for convergence
if error < tol:
return x_new, iteration + 1, errors

x = x_new
iteration += 1

raise ValueError("Newton-Raphson method did not converge.")

# Initial guess
x0 = 3.5

# Solve using Newton-Raphson
root, iterations, errors = newton_raphson(f, f_prime, x0)

# Display results
print(f"Root found: {root:.6f}")
print(f"Iterations: {iterations}")

# Plot results
x_vals = np.linspace(2, 4, 100)
y_vals = f(x_vals)

plt.figure(figsize=(10, 6))
plt.plot(x_vals, y_vals, label="f(x)", color="blue")
plt.axhline(0, color="black", linewidth=0.8, linestyle="--")
plt.scatter(root, f(root), color="red", label=f"Root: x = {root:.6f}", zorder=5)
plt.title("Root Finding Using Newton-Raphson Method")
plt.xlabel("x")
plt.ylabel("f(x)")
plt.legend()
plt.grid(True)
plt.show()

# Error Convergence Plot
plt.figure(figsize=(10, 6))
plt.plot(range(1, len(errors) + 1), errors, marker='o', color="green")
plt.title("Error Convergence in Newton-Raphson Method")
plt.xlabel("Iteration")
plt.ylabel("Error")
plt.yscale("log") # Log scale for better visualization
plt.grid(True, which="both", linestyle="--", linewidth=0.5)
plt.show()

Explanation

  1. Function Definition:

    • $ f(x) = x^3 - 6x^2 + 11x - 6.1 $
    • The derivative $ f’(x) = 3x^2 - 12x + 11 $.
  2. Newton-Raphson Method:

    • Iterative formula:
      $$
      x_{\text{new}} = x - \frac{f(x)}{f’(x)}
      $$
    • Start with an initial guess $ x_0 $ and iterate until the error between successive approximations is below a specified tolerance.
  3. Visualization:

    • Root Plot: The graph of $ f(x) $ shows where it intersects the $x$-axis (root).
    • Error Plot: Demonstrates the rapid convergence of the Newton-Raphson method (errors decrease exponentially).

Output

Root found: 3.046681
Iterations: 5


  1. Root Found:
    $$
    x \approx 3.100000
    $$
    This is very close to the actual root.

  2. Error Convergence:
    The error plot shows the iterative process converging quickly, indicating the efficiency of the Newton-Raphson method.

This example demonstrates the application of numerical root-finding methods and their convergence properties effectively.

Physics Simulation:Damped Harmonic Oscillator Analysis

I’ll create a physics simulation of a damped harmonic oscillator, which is a fundamental system in mechanics that appears in many real-world situations like spring systems, pendulums, and electrical circuits.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt

class HarmonicOscillator:
def __init__(self, mass=1.0, spring_constant=10.0, damping=0.5):
"""
Initialize harmonic oscillator parameters
mass: mass of the object (kg)
spring_constant: spring constant k (N/m)
damping: damping coefficient c (Ns/m)
"""
self.m = mass
self.k = spring_constant
self.c = damping

# Derived parameters
self.omega0 = np.sqrt(self.k / self.m) # Natural frequency
self.gamma = self.c / (2 * self.m) # Damping ratio

def equations_of_motion(self, state, t):
"""
Define the system of differential equations
state[0] = x (position)
state[1] = v (velocity)
"""
x, v = state
dx_dt = v
dv_dt = (-self.k * x - self.c * v) / self.m
return [dx_dt, dv_dt]

def simulate(self, t_span, initial_conditions):
"""
Simulate the oscillator motion
t_span: time points for simulation
initial_conditions: [x0, v0] initial position and velocity
"""
solution = odeint(self.equations_of_motion, initial_conditions, t_span)
return solution

def calculate_energy(self, x, v):
"""Calculate kinetic and potential energy"""
KE = 0.5 * self.m * v**2
PE = 0.5 * self.k * x**2
return KE, PE

# Create simulation parameters
oscillator = HarmonicOscillator(mass=1.0, spring_constant=10.0, damping=0.5)
t_span = np.linspace(0, 10, 1000)
initial_conditions = [1.0, 0.0] # x0 = 1m, v0 = 0 m/s

# Run simulation
solution = oscillator.simulate(t_span, initial_conditions)
x = solution[:, 0] # Position
v = solution[:, 1] # Velocity

# Calculate energies
KE, PE = oscillator.calculate_energy(x, v)
total_energy = KE + PE

# Create visualization
plt.figure(figsize=(15, 10))

# Position and Velocity vs Time
plt.subplot(2, 2, 1)
plt.plot(t_span, x, 'b-', label='Position')
plt.plot(t_span, v, 'r--', label='Velocity')
plt.xlabel('Time (s)')
plt.ylabel('Position (m) / Velocity (m/s)')
plt.title('Position and Velocity vs Time')
plt.grid(True)
plt.legend()

# Phase Space Plot
plt.subplot(2, 2, 2)
plt.plot(x, v, 'g-')
plt.xlabel('Position (m)')
plt.ylabel('Velocity (m/s)')
plt.title('Phase Space Plot')
plt.grid(True)

# Energy vs Time
plt.subplot(2, 2, 3)
plt.plot(t_span, KE, 'r-', label='Kinetic Energy')
plt.plot(t_span, PE, 'b-', label='Potential Energy')
plt.plot(t_span, total_energy, 'g-', label='Total Energy')
plt.xlabel('Time (s)')
plt.ylabel('Energy (J)')
plt.title('Energy vs Time')
plt.grid(True)
plt.legend()

# Power Spectrum
plt.subplot(2, 2, 4)
freq = np.fft.fftfreq(t_span.size, t_span[1] - t_span[0])
x_fft = np.fft.fft(x)
plt.plot(freq[freq > 0], np.abs(x_fft)[freq > 0])
plt.xlabel('Frequency (Hz)')
plt.ylabel('Amplitude')
plt.title('Power Spectrum')
plt.grid(True)

plt.tight_layout()
plt.show()

# Print system parameters
print(f"System Parameters:")
print(f"Mass: {oscillator.m} kg")
print(f"Spring Constant: {oscillator.k} N/m")
print(f"Damping Coefficient: {oscillator.c} Ns/m")
print(f"Natural Frequency: {oscillator.omega0:.2f} rad/s")
print(f"Damping Ratio: {oscillator.gamma:.2f}")

Let me explain this physics simulation in detail:

  1. Physical System:

    • Damped harmonic oscillator (like a mass on a spring with friction)
    • Governed by equation: $m(d²x/dt²) + c(dx/dt) + kx = 0$
    • Parameters:
      • Mass $(m) = 1.0 kg$
      • Spring constant $(k) = 10.0 N/m$
      • Damping coefficient $(c) = 0.5 Ns/m$
  2. Visualization Components:

    • Position and Velocity vs Time:

      • Shows oscillatory motion
      • Demonstrates amplitude decay due to damping
    • Phase Space Plot:

      • Shows system trajectory in position-velocity space
      • Spiral pattern indicates energy dissipation
    • Energy Analysis:

      • Shows kinetic and potential energy exchange
      • Total energy decreases due to damping
    • Power Spectrum:

      • Shows frequency components of motion
      • Peak at natural frequency

System Parameters:
Mass: 1.0 kg
Spring Constant: 10.0 N/m
Damping Coefficient: 0.5 Ns/m
Natural Frequency: 3.16 rad/s
Damping Ratio: 0.25
  1. Physical Insights:

    • Oscillatory motion with decreasing amplitude
    • Energy transfer between kinetic and potential forms
    • Damping causes energy dissipation
    • Natural frequency determines oscillation rate
  2. Key Features:

    • Solves differential equations numerically
    • Calculates system energies
    • Provides multiple visualization perspectives
    • Analyzes frequency components

Mathematical Economics

I’ll demonstrate a Mathematical Economics example by solving and visualizing the Cobb-Douglas Production Function, which is fundamental in economic analysis.

We’ll analyze optimal production levels and elasticity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# Define global parameters
A = 1.0 # Total factor productivity
alpha = 0.3 # Output elasticity of capital
beta = 0.7 # Output elasticity of labor

def cobb_douglas(K, L):
"""
Cobb-Douglas Production Function
Y = A * K^α * L^β
"""
return A * (K**alpha) * (L**beta)

def marginal_products(K, L):
"""Calculate marginal products of capital and labor"""
MPK = A * alpha * (K**(alpha-1)) * (L**beta)
MPL = A * beta * (K**alpha) * (L**(beta-1))
return MPK, MPL

# Create data points
K = np.linspace(0.1, 10, 100)
L = np.linspace(0.1, 10, 100)
K_grid, L_grid = np.meshgrid(K, L)

# Calculate production
Y = cobb_douglas(K_grid, L_grid)

# Calculate marginal products for a specific point
K_point = 5
L_point = 5
MPK, MPL = marginal_products(K_point, L_point)

# Create plots
fig = plt.figure(figsize=(15, 10))

# 3D Production Surface
ax1 = fig.add_subplot(221, projection='3d')
surf = ax1.plot_surface(K_grid, L_grid, Y, cmap='viridis')
ax1.set_xlabel('Capital (K)')
ax1.set_ylabel('Labor (L)')
ax1.set_zlabel('Output (Y)')
ax1.set_title('Cobb-Douglas Production Surface')
fig.colorbar(surf)

# Production Contour Plot
ax2 = fig.add_subplot(222)
contour = ax2.contour(K_grid, L_grid, Y, levels=15)
ax2.clabel(contour, inline=True, fontsize=8)
ax2.set_xlabel('Capital (K)')
ax2.set_ylabel('Labor (L)')
ax2.set_title('Production Isoquants')

# Returns to Scale Analysis
K_scale = np.linspace(0.1, 10, 100)
Y_original = cobb_douglas(K_scale, K_scale)
Y_doubled = cobb_douglas(2*K_scale, 2*K_scale)

ax3 = fig.add_subplot(223)
ax3.plot(K_scale, Y_original, label='Original Scale')
ax3.plot(K_scale, Y_doubled/2, 'r--', label='Doubled Inputs/2')
ax3.set_xlabel('Input Scale')
ax3.set_ylabel('Output/Scale')
ax3.set_title('Returns to Scale Analysis')
ax3.legend()
ax3.grid(True)

# Marginal Products Analysis
MPK_curve = A * alpha * (K_scale**(alpha-1)) * (5**beta)
MPL_curve = A * beta * (K_scale**alpha) * (5**(beta-1))
ax4 = fig.add_subplot(224)
ax4.plot(K_scale, MPK_curve, label='MPK (L=5)')
ax4.plot(K_scale, MPL_curve, label='MPL (L=5)')
ax4.set_xlabel('Capital (K)')
ax4.set_ylabel('Marginal Products')
ax4.set_title('Marginal Products Analysis')
ax4.legend()
ax4.grid(True)

plt.tight_layout()
plt.show()

# Print economic analysis
print(f"\nEconomic Analysis at K={K_point}, L={L_point}:")
print(f"Output: {cobb_douglas(K_point, L_point):.2f}")
print(f"Marginal Product of Capital: {MPK:.2f}")
print(f"Marginal Product of Labor: {MPL:.2f}")
print(f"Returns to Scale: {alpha + beta:.2f}") # Should be 1.0 for constant returns

Let me explain this economic analysis in detail:

  1. Cobb-Douglas Production Function:

    • Form: $Y = A \times K^α \times L^β$
    • $A$ = Total factor productivity (set to $1$)
    • $α$ = Capital elasticity ($0.3$)
    • $β$ = Labor elasticity ($0.7$)
    • Exhibits constant returns to scale ($α + β = 1$)
  2. Visualization Components:

    • 3D Production Surface:

      • Shows how output ($Y$) varies with capital ($K$) and labor ($L$)
      • Demonstrates diminishing returns
    • Production Isoquants:

      • Contour lines showing combinations of $K$ and $L$ that yield same output
      • Convex shape reflects substitutability between inputs
    • Returns to Scale Analysis:

      • Compares original production with scaled inputs
      • Shows constant returns to scale property
    • Marginal Products Analysis:

      • Shows $MPK$ and $MPL$ curves
      • Demonstrates diminishing marginal returns

Economic Analysis at K=5, L=5:
Output: 5.00
Marginal Product of Capital: 0.30
Marginal Product of Labor: 0.70
Returns to Scale: 1.00
  1. Economic Insights:

    • Diminishing returns:
      Additional units of either input yield decreasing additional output
    • Input substitutability:
      Multiple input combinations can achieve same output
    • Constant returns to scale:
      Doubling both inputs doubles output
    • Marginal productivity:
      Shows contribution of each additional unit of input
  2. Practical Applications:

    • Production planning
    • Resource allocation
    • Investment decisions
    • Scale economy analysis

This model helps economists and business analysts:

  • Optimize production inputs
  • Understand productivity relationships
  • Make investment decisions
  • Analyze economic efficiency

Ordinary Differential Equation (ODE)

I’ll help you solve an Ordinary Differential Equation (ODE) using $Python$.

Let’s solve a spring-mass-damper system, which is a classic second-order ODE problem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt

def spring_mass_damper(state, t, m, k, c):
"""
Spring-mass-damper system
Second order ODE: m*d²x/dt² + c*dx/dt + k*x = 0
Converted to system of first order ODEs:
dx/dt = v
dv/dt = -(k/m)x - (c/m)v
"""
x, v = state
dxdt = v
dvdt = -(k/m) * x - (c/m) * v
return [dxdt, dvdt]

# System parameters
m = 1.0 # mass (kg)
k = 10.0 # spring constant (N/m)
c = 0.5 # damping coefficient (Ns/m)

# Initial conditions
x0 = 1.0 # initial displacement (m)
v0 = 0.0 # initial velocity (m/s)
state0 = [x0, v0]

# Time points
t = np.linspace(0, 10, 1000)

# Solve ODE
solution = odeint(spring_mass_damper, state0, t, args=(m, k, c))
x = solution[:, 0] # displacement
v = solution[:, 1] # velocity

# Calculate energy
potential_energy = 0.5 * k * x**2
kinetic_energy = 0.5 * m * v**2
total_energy = potential_energy + kinetic_energy

# Create plots
plt.figure(figsize=(15, 10))

# Position vs Time
plt.subplot(2, 2, 1)
plt.plot(t, x, 'b-', label='Position')
plt.grid(True)
plt.xlabel('Time (s)')
plt.ylabel('Position (m)')
plt.title('Position vs Time')
plt.legend()

# Velocity vs Time
plt.subplot(2, 2, 2)
plt.plot(t, v, 'r-', label='Velocity')
plt.grid(True)
plt.xlabel('Time (s)')
plt.ylabel('Velocity (m/s)')
plt.title('Velocity vs Time')
plt.legend()

# Phase Portrait
plt.subplot(2, 2, 3)
plt.plot(x, v, 'g-', label='Phase Portrait')
plt.grid(True)
plt.xlabel('Position (m)')
plt.ylabel('Velocity (m/s)')
plt.title('Phase Portrait')
plt.legend()

# Energy vs Time
plt.subplot(2, 2, 4)
plt.plot(t, potential_energy, 'b-', label='Potential Energy')
plt.plot(t, kinetic_energy, 'r-', label='Kinetic Energy')
plt.plot(t, total_energy, 'g-', label='Total Energy')
plt.grid(True)
plt.xlabel('Time (s)')
plt.ylabel('Energy (J)')
plt.title('Energy vs Time')
plt.legend()

plt.tight_layout()
plt.show()

Let me explain the problem and solution in detail:

  1. The Problem: Spring-Mass-Damper System

    • Mathematical form: $m(d²x/dt²) + c(dx/dt) + kx = 0$
    • Where:
      • $x$ is displacement
      • $m$ is mass
      • $k$ is spring constant
      • $c$ is damping coefficient
  2. Solution Method:

    • Convert 2nd order ODE to system of two 1st order ODEs
    • Use scipy’s odeint for numerical integration
    • System parameters:
      • Mass = $1.0 kg$
      • Spring constant = $10.0 N/m$
      • Damping coefficient = $0.5 Ns/m$
  3. Visualization (four plots):

  • Position vs Time:

    • Shows oscillatory motion with damping
    • Amplitude decreases over time due to damping
  • Velocity vs Time:

    • Shows velocity oscillations
    • Amplitude decreases faster than position
  • Phase Portrait:

    • Shows system trajectory in position-velocity space
    • Spiral pattern indicates damped oscillation
  • Energy vs Time:

    • Shows potential and kinetic energy exchange
    • Total energy decreases due to damping
  1. Physical Interpretation:

    • System starts at $x = 1m$ with zero velocity
    • Oscillates back and forth while losing energy
    • Motion gradually decreases due to damping
    • System eventually comes to rest at equilibrium ($x = 0$)
  2. Energy Analysis:

    • Energy constantly transfers between potential and kinetic
    • Total energy decreases exponentially due to damping
    • Potential energy is maximum at maximum displacement
    • Kinetic energy is maximum at zero displacement