pendulum2.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. """
  2. Modified version of pendulum1.py:
  3. A function draws the free body diagram, given the angle.
  4. This function can be coupled to a numerical solver
  5. for the motion. Videos of the motion are made.
  6. """
  7. from pysketcher import *
  8. H = 15.
  9. W = 17.
  10. drawing_tool.set_coordinate_system(xmin=0, xmax=W,
  11. ymin=0, ymax=H,
  12. axis=False)
  13. def pendulum(theta, S, mg, drag, t, time_level):
  14. drawing_tool.set_linecolor('blue')
  15. import math
  16. a = math.degrees(theta[time_level])
  17. L = 0.4*H # length
  18. P = (W/2, 0.8*H) # rotation point
  19. vertical = Line(P, P-point(0,L))
  20. path = Arc(P, L, -90, a)
  21. angle = Arc_wText(r'$\theta$', P, L/4, -90, a, text_spacing=1/30.)
  22. mass_pt = path.geometric_features()['end']
  23. rod = Line(P, mass_pt)
  24. mass = Circle(center=mass_pt, radius=L/20.)
  25. mass.set_filled_curves(color='blue')
  26. rod_vec = rod.geometric_features()['end'] - \
  27. rod.geometric_features()['start']
  28. unit_rod_vec = unit_vec(rod_vec)
  29. mass_symbol = Text('$m$', mass_pt + L/10*unit_rod_vec)
  30. length = Distance_wText(P, mass_pt, '$L$')
  31. # Displace length indication
  32. length.translate(L/15*point(cos(radians(a)), sin(radians(a))))
  33. gravity = Gravity(start=P+point(0.8*L,0), length=L/3)
  34. def set_dashed_thin_blackline(*objects):
  35. """Set linestyle of objects to dashed, black, width=1."""
  36. for obj in objects:
  37. obj.set_linestyle('dashed')
  38. obj.set_linecolor('black')
  39. obj.set_linewidth(1)
  40. set_dashed_thin_blackline(vertical, path)
  41. fig = Composition(
  42. {'body': mass, 'rod': rod,
  43. 'vertical': vertical, 'theta': angle, 'path': path,
  44. 'g': gravity, 'L': length})
  45. #fig.draw()
  46. #drawing_tool.display()
  47. #drawing_tool.savefig('tmp_pendulum1')
  48. drawing_tool.set_linecolor('black')
  49. rod_start = rod.geometric_features()['start'] # Point P
  50. vertical2 = Line(rod_start, rod_start + point(0,-L/3))
  51. set_dashed_thin_blackline(vertical2)
  52. set_dashed_thin_blackline(rod)
  53. angle2 = Arc_wText(r'$\theta$', rod_start, L/6, -90, a,
  54. text_spacing=1/30.)
  55. magnitude = 1.2*L/2 # length of a unit force in figure
  56. force = mg[time_level] # constant (scaled eq: about 1)
  57. force *= magnitude
  58. mg_force = Force(mass_pt, mass_pt + force*point(0,-1),
  59. '', text_pos='end')
  60. force = S[time_level]
  61. force *= magnitude
  62. rod_force = Force(mass_pt, mass_pt - force*unit_vec(rod_vec),
  63. '', text_pos='end',
  64. text_spacing=(0.03, 0.01))
  65. force = drag[time_level]
  66. force *= magnitude
  67. #print('drag(%g)=%g' % (t, drag[time_level]))
  68. air_force = Force(mass_pt, mass_pt -
  69. force*unit_vec((rod_vec[1], -rod_vec[0])),
  70. '', text_pos='end',
  71. text_spacing=(0.04,0.005))
  72. body_diagram = Composition(
  73. {'mg': mg_force, 'S': rod_force, 'air': air_force,
  74. 'rod': rod,
  75. 'vertical': vertical2, 'theta': angle2,
  76. 'body': mass})
  77. x0y0 = Text('$(x_0,y_0)$', P + point(-0.4,-0.1))
  78. ir = Force(P, P + L/10*unit_vec(rod_vec),
  79. r'$\boldsymbol{i}_r$', text_pos='end',
  80. text_spacing=(0.015,0))
  81. ith = Force(P, P + L/10*unit_vec((-rod_vec[1], rod_vec[0])),
  82. r'$\boldsymbol{i}_{\theta}$', text_pos='end',
  83. text_spacing=(0.02,0.005))
  84. #body_diagram['ir'] = ir
  85. #body_diagram['ith'] = ith
  86. #body_diagram['origin'] = x0y0
  87. drawing_tool.erase()
  88. body_diagram.draw(verbose=0)
  89. #drawing_tool.display('Free body diagram')
  90. drawing_tool.savefig('tmp_%04d.png' % time_level, crop=False)
  91. # No cropping: otherwise movies will be very strange
  92. def simulate_pendulum(alpha, theta0, dt, T):
  93. import odespy
  94. def f(u, t, alpha):
  95. omega, theta = u
  96. return [-alpha*omega*abs(omega) - sin(theta),
  97. omega]
  98. import numpy as np
  99. Nt = int(round(T/float(dt)))
  100. t = np.linspace(0, Nt*dt, Nt+1)
  101. solver = odespy.RK4(f, f_args=[alpha])
  102. solver.set_initial_condition([0, theta0])
  103. u, t = solver.solve(t,
  104. terminate=lambda u, t, n: abs(u[n,1]) < 1E-3)
  105. omega = u[:,0]
  106. theta = u[:,1]
  107. S = omega**2 + np.cos(theta)
  108. drag = -alpha*np.abs(omega)*omega
  109. return t, theta, omega, S, drag
  110. def animate():
  111. # Clean up old plot files
  112. import os, glob
  113. for filename in glob.glob('tmp_*.png') + glob.glob('movie.*'):
  114. os.remove(filename)
  115. # Solve problem
  116. from math import pi, radians, degrees
  117. import numpy as np
  118. alpha = 0.4
  119. period = 2*pi
  120. T = 12*period
  121. dt = period/40
  122. a = 70
  123. theta0 = radians(a)
  124. t, theta, omega, S, drag = simulate_pendulum(alpha, theta0, dt, T)
  125. mg = np.ones(S.size)
  126. # Visualize drag force 5 times as large
  127. drag *= 5
  128. print('NOTE: drag force magnified 5 times!!')
  129. # Draw animation
  130. import time
  131. for time_level, t_ in enumerate(t):
  132. pendulum(theta, S, mg, drag, t_, time_level)
  133. time.sleep(0.2)
  134. # Make videos
  135. prog = 'ffmpeg'
  136. filename = 'tmp_%04d.png'
  137. fps = 6
  138. codecs = {'flv': 'flv', 'mp4': 'libx264',
  139. 'webm': 'libvpx', 'ogg': 'libtheora'}
  140. for ext in codecs:
  141. lib = codecs[ext]
  142. cmd = '%(prog)s -i %(filename)s -r %(fps)s ' % vars()
  143. cmd += '-vcodec %(lib)s movie.%(ext)s' % vars()
  144. print(cmd)
  145. os.system(cmd)
  146. if __name__ == '__main__':
  147. animate()