|
| 1 | +import math |
| 2 | +import sys |
| 3 | +import time |
| 4 | + |
| 5 | +try: |
| 6 | + import numpy as np |
| 7 | + import matplotlib.pyplot as plt |
| 8 | + from matplotlib.animation import FuncAnimation |
| 9 | +except ImportError: |
| 10 | + print("❌ This project requires numpy and matplotlib.") |
| 11 | + print("Install them using: pip install numpy matplotlib") |
| 12 | + sys.exit(1) |
| 13 | + |
| 14 | + |
| 15 | +def main(): |
| 16 | + print("=" * 58) |
| 17 | + print("📈 WELCOME TO FOURIER SERIES VISUALIZER 📈") |
| 18 | + print("=" * 58) |
| 19 | + print("Explore how complex waveforms can be approximated using harmonics.\n") |
| 20 | + |
| 21 | + while True: |
| 22 | + print("=" * 58) |
| 23 | + print("Choose a waveform type:") |
| 24 | + print("1️⃣ Square Wave") |
| 25 | + print("2️⃣ Sawtooth Wave") |
| 26 | + print("3️⃣ Triangle Wave") |
| 27 | + print("4️⃣ Exit") |
| 28 | + |
| 29 | + choice = input("🎯 Enter choice (1-4): ").strip() |
| 30 | + |
| 31 | + if choice == "4": |
| 32 | + print("\n👋 Exiting... Keep exploring math!\n") |
| 33 | + break |
| 34 | + |
| 35 | + if choice not in ["1", "2", "3"]: |
| 36 | + print("❌ Invalid choice. Please pick 1, 2, 3, or 4.\n") |
| 37 | + continue |
| 38 | + |
| 39 | + waveform_type = {"1": "square", "2": "sawtooth", "3": "triangle"}[choice] |
| 40 | + |
| 41 | + # Get number of harmonics |
| 42 | + while True: |
| 43 | + harmonics_in = input("📝 Enter number of harmonics (N) (1-50): ").strip() |
| 44 | + try: |
| 45 | + n_harmonics = int(harmonics_in) |
| 46 | + if n_harmonics < 1: |
| 47 | + print("⚠️ Enter a value greater than or equal to 1.") |
| 48 | + continue |
| 49 | + if n_harmonics > 50: |
| 50 | + print("⚠️ For performance, keep harmonics up to 50.") |
| 51 | + continue |
| 52 | + break |
| 53 | + except ValueError: |
| 54 | + print("⚠️ Invalid number. Try again.") |
| 55 | + |
| 56 | + print(f"\n⏳ Simulating {waveform_type} wave with {n_harmonics} harmonics...") |
| 57 | + time.sleep(0.6) |
| 58 | + |
| 59 | + print("\n🖥️ Opening animation window... Close it to return to menu.") |
| 60 | + |
| 61 | + # Setup figure |
| 62 | + fig, ax = plt.subplots(figsize=(10, 5)) |
| 63 | + ax.set_title(f"Fourier Series Approximation: {waveform_type.capitalize()} Wave (N={n_harmonics})") |
| 64 | + |
| 65 | + # We'll draw circles on the left and the wave on the right |
| 66 | + ax.set_xlim(-3, 10) |
| 67 | + ax.set_ylim(-3, 3) |
| 68 | + ax.set_aspect('equal', adjustable='box') |
| 69 | + ax.grid(True, linestyle='--', alpha=0.5) |
| 70 | + |
| 71 | + # Plot elements |
| 72 | + wave_line, = ax.plot([], [], color="#10b981", linewidth=2, label="Approximated Wave") |
| 73 | + connect_line, = ax.plot([], [], color="gray", linestyle="--", alpha=0.5) |
| 74 | + drawing_point, = ax.plot([], [], 'ro', markersize=4) |
| 75 | + |
| 76 | + circles = [] |
| 77 | + radii_lines = [] |
| 78 | + for _ in range(n_harmonics): |
| 79 | + circle, = ax.plot([], [], color="blue", alpha=0.2) |
| 80 | + circles.append(circle) |
| 81 | + radius_line, = ax.plot([], [], color="blue", alpha=0.5) |
| 82 | + radii_lines.append(radius_line) |
| 83 | + |
| 84 | + time_val = 0 |
| 85 | + wave_data = [] |
| 86 | + |
| 87 | + def update(frame): |
| 88 | + nonlocal time_val, wave_data |
| 89 | + |
| 90 | + x = 0 |
| 91 | + y = 0 |
| 92 | + |
| 93 | + for i in range(n_harmonics): |
| 94 | + prev_x = x |
| 95 | + prev_y = y |
| 96 | + |
| 97 | + n = 0 |
| 98 | + radius = 0 |
| 99 | + |
| 100 | + if waveform_type == 'square': |
| 101 | + n = i * 2 + 1 |
| 102 | + radius = (4 / (n * math.pi)) |
| 103 | + elif waveform_type == 'sawtooth': |
| 104 | + n = i + 1 |
| 105 | + radius = (2 * (-1)**(n + 1) / (n * math.pi)) |
| 106 | + elif waveform_type == 'triangle': |
| 107 | + n = i * 2 + 1 |
| 108 | + radius = (8 * (-1)**((n - 1) / 2) / ((n**2) * (math.pi**2))) |
| 109 | + |
| 110 | + x += radius * math.cos(n * time_val) |
| 111 | + y += radius * math.sin(n * time_val) |
| 112 | + |
| 113 | + # Draw circle |
| 114 | + circle_theta = np.linspace(0, 2*np.pi, 50) |
| 115 | + circles[i].set_data(prev_x + abs(radius)*np.cos(circle_theta), |
| 116 | + prev_y + abs(radius)*np.sin(circle_theta)) |
| 117 | + |
| 118 | + # Draw radius line |
| 119 | + radii_lines[i].set_data([prev_x, x], [prev_y, y]) |
| 120 | + |
| 121 | + wave_data.insert(0, y) |
| 122 | + if len(wave_data) > 300: |
| 123 | + wave_data.pop() |
| 124 | + |
| 125 | + wave_x = np.linspace(3, 3 + len(wave_data) * 0.03, len(wave_data)) |
| 126 | + wave_line.set_data(wave_x, wave_data) |
| 127 | + |
| 128 | + connect_line.set_data([x, 3], [y, wave_data[0]]) |
| 129 | + drawing_point.set_data([x], [y]) |
| 130 | + |
| 131 | + time_val += 0.03 |
| 132 | + |
| 133 | + # Matplotlib requires returning an iterable of updated artists |
| 134 | + return [wave_line, connect_line, drawing_point] + circles + radii_lines |
| 135 | + |
| 136 | + ani = FuncAnimation(fig, update, frames=400, interval=20, blit=True) |
| 137 | + plt.show() |
| 138 | + |
| 139 | +if __name__ == "__main__": |
| 140 | + main() |
0 commit comments