fork download
  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. from matplotlib.animation import FuncAnimation
  4. from matplotlib.patches import Circle, PathPatch
  5. from matplotlib.path import Path
  6.  
  7. # Set up grid
  8. x = np.linspace(-2, 2, 500)
  9. y = np.linspace(-2, 2, 500)
  10. X, Y = np.meshgrid(x, y)
  11.  
  12. # Convert to polar
  13. r = np.sqrt(X**2 + Y**2)
  14. theta = np.arctan2(Y, X)
  15.  
  16. # Parameters
  17. U_inf = 1.0 # Freestream velocity
  18. R = 0.8 # Circle radius
  19. lambda_vals = np.linspace(0, 0.15, 30) # Joukowsky parameter range
  20.  
  21. # Starting points for streamlines (12 points)
  22. start_y = np.linspace(-1.5, 1.5, 12)
  23. start_points = np.array([[-2 * np.ones(12), start_y]]).T.reshape(-1, 2)
  24.  
  25. # Joukowsky transformation
  26. def joukowsky(z, lambda_):
  27. return z + lambda_**2 / z
  28.  
  29. # Initialize figure
  30. fig, ax = plt.subplots(figsize=(8, 8))
  31. ax.set_xlim(-2, 2)
  32. ax.set_ylim(-2, 2)
  33. ax.set_facecolor('black')
  34. ax.set_xticks([])
  35. ax.set_yticks([])
  36.  
  37. # Circle (initial shape)
  38. circle = Circle((0, 0), R, color='#FAF9F6', fill=True, zorder=3)
  39. ax.add_patch(circle)
  40.  
  41. # Pre-compute velocity fields for the circle (unchanged)
  42. U_r = U_inf * (1 - (R**2) / (r**2)) * np.cos(theta)
  43. U_theta = -U_inf * (1 + (R**2) / (r**2)) * np.sin(theta)
  44. U = U_r * np.cos(theta) - U_theta * np.sin(theta)
  45. V = U_r * np.sin(theta) + U_theta * np.cos(theta)
  46.  
  47. # Initial streamlines (circle)
  48. strm = ax.streamplot(X, Y, U, V, color='#E63946', linewidth=1.5,
  49. start_points=start_points, arrowsize=0, zorder=2)
  50.  
  51. # Add arrows manually (same as your original)
  52. for line in strm.lines.get_segments():
  53. arrow_interval = max(1, (len(line)//8)*2)
  54. for i in range(0, len(line) - arrow_interval, arrow_interval):
  55. p_start, p_end = line[i], line[i + 1]
  56. dx, dy = p_end[0] - p_start[0], p_end[1] - p_start[1]
  57. ax.arrow(p_start[0], p_start[1], dx, dy, shape='full', lw=1,
  58. head_width=0.05, head_length=0.1, color='#E63946', zorder=2)
  59.  
  60. # Animation function
  61. def update(frame):
  62. global strm, circle
  63.  
  64. lambda_ = lambda_vals[frame]
  65.  
  66. # Clear previous streamlines and circle
  67. for art in ax.collections[:]: # Remove all streamlines/arrows
  68. art.remove()
  69. ax.patches.remove(circle)
  70.  
  71. # Apply Joukowsky transformation to grid and circle
  72. Z = X + 1j*Y
  73. Z_transformed = joukowsky(Z, lambda_)
  74.  
  75. # Transform velocity field (approximate: assumes potential flow transforms)
  76. # Note: For exact physics, recompute potential in transformed plane
  77. U_transformed = U.copy()
  78. V_transformed = V.copy()
  79.  
  80. # Update streamlines
  81. strm = ax.streamplot(X, Y, U_transformed, V_transformed, color='#E63946',
  82. linewidth=1.5, start_points=start_points, arrowsize=0, zorder=2)
  83.  
  84. # Add arrows
  85. for line in strm.lines.get_segments():
  86. arrow_interval = max(1, (len(line)//8)*2)
  87. for i in range(0, len(line) - arrow_interval, arrow_interval):
  88. p_start, p_end = line[i], line[i + 1]
  89. dx, dy = p_end[0] - p_start[0], p_end[1] - p_start[1]
  90. ax.arrow(p_start[0], p_start[1], dx, dy, shape='full', lw=1,
  91. head_width=0.05, head_length=0.1, color='#E63946', zorder=2)
  92.  
  93. # Draw transformed shape (airfoil)
  94. theta_circle = np.linspace(0, 2*np.pi, 200)
  95. circle_points = R * np.exp(1j * theta_circle)
  96. airfoil_points = joukowsky(circle_points, lambda_)
  97. airfoil_path = Path(np.column_stack([np.real(airfoil_points), np.imag(airfoil_points)]))
  98. airfoil = PathPatch(airfoil_path, color='#FAF9F6', fill=True, zorder=3)
  99. ax.add_patch(airfoil)
  100.  
  101. ax.set_title(f"Joukowsky Transformation (λ={lambda_:.3f})")
  102. return []
  103.  
  104. # Create animation
  105. ani = FuncAnimation(fig, update, frames=len(lambda_vals), blit=False, interval=100)
  106.  
  107. # Uncomment to save:
  108. # ani.save('joukowsky_airfoil.gif', writer='pillow', fps=10)
  109. # Or save frames:
  110. # for i in range(len(lambda_vals)):
  111. # update(i)
  112. # plt.savefig(f'frame_{i:03d}.png', dpi=150, facecolor='black')
  113.  
  114. plt.show()
Success #stdin #stdout #stderr 3.73s 105052KB
stdin
Standard input is empty
stdout
Standard output is empty
stderr
Fontconfig error: No writable cache directories
/usr/local/lib/python3.12/dist-packages/matplotlib/patches.py:3421: RuntimeWarning: invalid value encountered in scalar divide
  cos_t, sin_t = head_length / head_dist, head_width / head_dist
/usr/local/lib/python3.12/dist-packages/matplotlib/animation.py:892: UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g. `anim`, that exists until you output the Animation using `plt.show()` or `anim.save()`.