Advertisement
15Redstones

Untitled

Aug 9th, 2023
1,317
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.06 KB | Science | 0 0
  1. import os
  2.  
  3. import matplotlib.pyplot as plt
  4. import numpy as np
  5. from matplotlib.animation import FuncAnimation
  6. from tqdm import trange
  7.  
  8. # Create the "plots" directory if it doesn't exist
  9. if not os.path.exists("plots"):
  10.     os.makedirs("plots")
  11.  
  12.  
  13. def yes_or_no(question):
  14.     while True:
  15.         choice = input(question + " (y/n): ").strip().lower()
  16.         if choice in ['y', 'yes']:
  17.             return True
  18.         elif choice in ['n', 'no']:
  19.             return False
  20.         else:
  21.             print("Please enter 'y' or 'n'.")
  22.  
  23.  
  24. friction = yes_or_no("Use air friction?")
  25.  
  26. if friction:
  27.     prefix = "with air resistance"
  28.     pre_short = "f"
  29. else:
  30.     prefix = "in vacuum"
  31.     pre_short = "v"
  32.  
  33. # Constants
  34. # For air drag calculations
  35. mass = 0.05  # 50 g
  36. cd = 0.7
  37. r = 0.068 / 2  # 6.8 cm diameter
  38. rho = 1.293  # kg/m³ air density
  39. gamma = 0.5 * rho * cd * np.pi * r ** 2
  40.  
  41. # For pull-in process
  42. original_r = 1
  43. target_r = 0.1
  44. pull_speed = 0.05  # 5 cm/sec
  45. # define r(t) function for pull-in
  46. desired_r = lambda t: np.max([original_r - np.max([0, pull_speed * (t - 3)]), target_r])
  47.  
  48. # initial velocity
  49. original_RPM = 120
  50. initial_position = np.array([original_r, 0.0])
  51. initial_velocity = np.array([0.0, original_RPM / 60 * (2 * np.pi * original_r)])
  52.  
  53. # simulation settings
  54. t_step = 5e-5  # smaller t_step is more accurate
  55. total_t = 50  # seconds
  56.  
  57. print(f"{gamma=}")
  58. if not friction:
  59.     gamma = 0
  60.  
  61.  
  62. class ForcePID:
  63.     # regulates force on string for pull-in process, basic PID controller
  64.     def __init__(self, f, kP=0, kI=0, kD=0):
  65.         self.kP, self.kI, self.kD = kP, kI, kD
  66.         self.rs = []
  67.         self.ts = []
  68.         self.errors = []
  69.         self.desiredFunc = f
  70.         self.integral = 0
  71.  
  72.     def __call__(self, r, t):
  73.         error = r - self.desiredFunc(t)
  74.  
  75.         P = self.kP * error
  76.         if len(self.ts) > 0:
  77.             self.integral += (t - self.ts[-1]) * error * self.kI
  78.             D = self.kD * (error - self.errors[-1]) / (t - self.ts[-1])
  79.         else:
  80.             D = 0, 0
  81.         I = self.integral
  82.  
  83.         self.rs.append(r)
  84.         self.ts.append(t)
  85.         self.errors.append(error)
  86.  
  87.         # print(error, P, I, D)
  88.  
  89.         return P + I + D
  90.  
  91.  
  92. forceStrength = ForcePID(desired_r, kP=25000, kI=100000, kD=1)
  93.  
  94.  
  95. # Forces
  96. def total_force(position, velocity, t):
  97.     # air drag
  98.     force1 = - gamma * velocity * np.linalg.norm(velocity)
  99.     # string force
  100.     force2 = - position / np.linalg.norm(position) * forceStrength(np.linalg.norm(position), t)
  101.     return force1 + force2
  102.  
  103.  
  104. # Initialize arrays to store data
  105. num_steps = int(total_t / t_step)
  106. positions = np.zeros((num_steps, 2))
  107. velocities = np.zeros((num_steps, 2))
  108. forces = np.zeros((num_steps, 2))
  109. time = np.arange(0, total_t, t_step)
  110.  
  111. # Initial conditions
  112. positions[0] = initial_position
  113. velocities[0] = initial_velocity
  114.  
  115. # Euler-Cromer integration
  116. for i in trange(1, num_steps):
  117.     force = total_force(positions[i - 1], velocities[i - 1], i * t_step)
  118.     acceleration = force / mass
  119.     velocities[i] = velocities[i - 1] + acceleration * t_step
  120.     positions[i] = positions[i - 1] + velocities[i] * t_step
  121.     forces[i] = force
  122.  
  123. # calculate observables
  124. L = np.cross(positions, mass * velocities)
  125. E = mass / 2 * np.sum(velocities ** 2, axis=1)
  126.  
  127. r = np.hypot(positions[:, 0], positions[:, 1])
  128. theta = np.arctan2(positions[:, 1], positions[:, 0])
  129.  
  130. omega = np.diff(theta) % (2 * np.pi) / t_step
  131. # omega = np.cross(positions, velocities) / np.linalg.norm(positions, axis=1)**2
  132. RPM = 60 * omega / (2 * np.pi)
  133.  
  134. # P = np.sum(forces * velocities, axis=1)
  135. P = - np.linalg.norm(forces, axis=1)[:-1] * np.diff(r) / t_step
  136. W = np.cumsum(P) * t_step
  137.  
  138. fig, axs = plt.subplots(3, 2, figsize=(10, 8))
  139. fig.suptitle(f"Ball on String {prefix}")
  140.  
  141. for ax in axs.flatten():
  142.     ax.grid(True)
  143.  
  144. axs[0, 0].plot(time, [desired_r(t) for t in time], label="desired")
  145. axs[0, 0].plot(time, r, label="real")
  146. # axs[0, 0].set_xlabel("t [s]")
  147. axs[0, 0].set_ylabel("r [m]")
  148. axs[0, 0].set_ylim([0, 1.2 * np.max(r)])
  149. axs[0, 0].legend()
  150.  
  151. axs[0, 1].plot(time, np.linalg.norm(velocities, axis=1))
  152. # axs[0, 1].set_xlabel("t [s]")
  153. axs[0, 1].set_ylabel("v [m/s]")
  154. axs[0, 1].set_ylim([0, 1.1 * np.max(np.linalg.norm(velocities, axis=1))])
  155.  
  156. axs[1, 0].plot(time, np.linalg.norm(forces, axis=1))
  157. # axs[1, 0].set_xlabel("t [s]")
  158. axs[1, 0].set_ylabel("F [N]")
  159. axs[1, 0].set_ylim([0, 1.1 * np.max(np.linalg.norm(forces, axis=1))])
  160.  
  161. axs[1, 1].plot(time[:-1], RPM)
  162. # axs[1, 1].set_xlabel("t [s]")
  163. axs[1, 1].set_ylabel("RPM")
  164. axs[1, 1].set_ylim([0, 1.1 * np.max(RPM)])
  165.  
  166. axs[2, 0].plot(time, E, label="E_kin")
  167. axs[2, 0].plot(time[:-1], W, label="W")
  168. axs[2, 0].plot(time[:-1], E[:-1] - W, label="E_kin - W")
  169.  
  170. axs[2, 0].set_xlabel("t [s]")
  171. axs[2, 0].set_ylabel("E [J]")
  172. axs[2, 0].legend()
  173.  
  174. axs[2, 1].plot(time, L)
  175. axs[2, 1].set_xlabel("t [s]")
  176. axs[2, 1].set_ylabel("L [kg m²/s]")
  177. axs[2, 1].set_ylim([0, 1.1 * np.max(L)])
  178.  
  179. # Animation
  180. fig, ax = plt.subplots(figsize=(6, 6))
  181. max_r = np.max(np.linalg.norm(positions, axis=1)) * 1.2
  182. ax.set_xlim(-max_r, max_r)
  183. ax.set_ylim(-max_r, max_r)
  184. ax.set_aspect('equal')
  185. ax.set_xlabel('X [m]')
  186. ax.set_ylabel('Y [m]')
  187. ax.set_title(f'Ball on String {prefix}')
  188. ax.grid(True)
  189.  
  190. trajectory, = ax.plot([], [], 'g--', linewidth=0.5, alpha=0.5)  # Trajectory trace
  191. line, = ax.plot([], [], 'b-', linewidth=1)  # Line connecting origin and ball
  192. ball, = ax.plot([], [], 'bo', markersize=10)
  193. velocity_arrow = ax.arrow(0, 0, 0, 0, color='red', width=0.01)
  194. time_text = ax.text(-max_r + 0.15, max_r - 0.15, '', fontsize=12)  # Text to display simulation time
  195.  
  196. frame_skip = int(1 / 20 / t_step)
  197.  
  198.  
  199. def init():
  200.     line.set_data([], [])
  201.     ball.set_data([], [])
  202.     trajectory.set_data([], [])
  203.     velocity_arrow.set_data(x=0, y=0, dx=0, dy=0)
  204.     time_text.set_text('')
  205.     return line, ball, trajectory, velocity_arrow, time_text
  206.  
  207.  
  208. def update(frame):
  209.     frame *= frame_skip
  210.     position = positions[frame]
  211.     velocity = velocities[frame] * 0.02
  212.  
  213.     if np.linalg.norm(velocity) > 0.5:
  214.         velocity *= 0.5 / np.linalg.norm(velocity)
  215.     assert np.linalg.norm(velocity) < 0.6
  216.  
  217.     ball.set_data(position)
  218.     line.set_data([0, position[0]], [0, position[1]])
  219.     trajectory.set_data(positions[:frame:(frame_skip // 20), 0], positions[:frame:(frame_skip // 20), 1])
  220.  
  221.     velocity_arrow.set_data(x=position[0], y=position[1], dx=velocity[0], dy=velocity[1])
  222.  
  223.     time_text.set_text(f't = {time[frame]:.1f}s')
  224.  
  225.     return line, ball, trajectory, velocity_arrow, time_text
  226.  
  227.  
  228. # Initialize the animation
  229. ani = FuncAnimation(fig, update, frames=int(num_steps / frame_skip * 0.6), init_func=init,
  230.                     interval=1000 * t_step * frame_skip, blit=True)
  231.  
  232. plt.show()
  233.  
  234. # Create a separate figure for each plot and save it
  235. fig1, ax1 = plt.subplots(figsize=(8, 6), dpi=300)
  236. ax1.grid(True)
  237. ax1.plot(time, [desired_r(t) for t in time], label="desired", alpha=0.5, linestyle="--", color="green")
  238. ax1.plot(time, r, label="real")
  239. ax1.set_xlabel("t [s]")
  240. ax1.set_ylabel("r [m]")
  241. ax1.set_ylim([0, 1.2 * np.max(r)])
  242. ax1.set_title(f"Desired vs. Real Radius ({prefix})")
  243. ax1.legend()
  244. fig1.savefig(f"plots/desired_real_plot_{pre_short}.png")
  245. plt.close(fig1)
  246.  
  247. fig2, ax2 = plt.subplots(figsize=(8, 6), dpi=300)
  248. ax2.grid(True)
  249. ax2.plot(time, np.linalg.norm(velocities, axis=1))
  250. ax2.set_xlabel("t [s]")
  251. ax2.set_ylabel("v [m/s]")
  252. ax2.set_ylim([0, 1.1 * np.max(np.linalg.norm(velocities, axis=1))])
  253. ax2.set_title(f"Velocity over Time ({prefix})")
  254. fig2.savefig(f"plots/velocity_plot_{pre_short}.png")
  255. plt.close(fig2)
  256.  
  257. fig3, ax3 = plt.subplots(figsize=(8, 6), dpi=300)
  258. ax3.grid(True)
  259. ax3.plot(time, np.linalg.norm(forces, axis=1))
  260. ax3.set_xlabel("t [s]")
  261. ax3.set_ylabel("F [N]")
  262. ax3.set_ylim([0, 1.1 * np.max(np.linalg.norm(forces, axis=1))])
  263. ax3.set_title(f"Force Magnitude over Time ({prefix})")
  264. fig3.savefig(f"plots/force_plot_{pre_short}.png")
  265. plt.close(fig3)
  266.  
  267. fig4, ax4 = plt.subplots(figsize=(8, 6), dpi=300)
  268. ax4.grid(True)
  269. ax4.plot(time[:-1], RPM)
  270. ax4.set_xlabel("t [s]")
  271. ax4.set_ylabel("RPM")
  272. ax4.set_ylim([0, 1.1 * np.max(RPM)])
  273. ax4.set_title(f"Rotations per Minute (RPM) ({prefix})")
  274. fig4.savefig(f"plots/RPM_plot_{pre_short}.png")
  275. plt.close(fig4)
  276.  
  277. fig5, ax5 = plt.subplots(figsize=(8, 6), dpi=300)
  278. ax5.grid(True)
  279. ax5.plot(time, E, label="Kinetic Energy")
  280. ax5.plot(time[:-1], W, label="Work done")
  281. ax5.plot(time[:-1], E[:-1] - W, label="Ekin - W")
  282. ax5.set_xlabel("t [s]")
  283. ax5.set_ylabel("E [J]")
  284. ax5.set_title(f"Energy Analysis ({prefix})")
  285. ax5.legend()
  286. fig5.savefig(f"plots/energy_plot_{pre_short}.png")
  287. plt.close(fig5)
  288.  
  289. fig6, ax6 = plt.subplots(figsize=(8, 6), dpi=300)
  290. ax6.grid(True)
  291. ax6.plot(time, L)
  292. ax6.set_xlabel("t [s]")
  293. ax6.set_ylabel("L [kg m²/s]")
  294. ax6.set_ylim([0, 1.1 * np.max(L)])
  295. ax6.set_title(f"Angular Momentum ({prefix})")
  296. fig6.savefig(f"plots/angular_momentum_plot_{pre_short}.png")
  297. plt.close(fig6)
  298.  
  299. print(f"saving {int(num_steps / frame_skip * 0.6)} frames")
  300. # Save the animation to a video file
  301. output_file = f'plots/video_{pre_short}.mp4'
  302. ani.save(output_file, writer='ffmpeg', fps=20)
  303. print("File write done!")
  304.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement