mechanizmos58

vpython_spinning_and_flipping_four_points_six_springs

Mar 1st, 2022
1,547
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.54 KB | None | 0 0
  1.  
  2. # vpython_spinning_and_flipping_four_points_six_springs_3.py
  3. # 31_12_21
  4. # ...
  5. # 02_03_22
  6.  
  7. import numpy as np
  8. from functools import partial
  9. import sys
  10. from vpython import *
  11.  
  12. '''
  13.  
  14. REFERENCES:
  15.  
  16. Ideas for a rigid body made from springs from:
  17.   Rhett Allain's Physics Explained videos
  18.  
  19. Shane Ross - Ross Dynamics Lab videos
  20.  
  21. Analytical Mechanics of Space Systems (Fourth Edition 2018)
  22. by Hanspeter Schaub & John L. Junkins
  23.  
  24. Classical Mechanics by John R. Taylor (2005)
  25.  
  26. An Introduction to the Coriolis Force
  27. by Henry M. Stommel & Dennis W. Moore (1989)
  28.  
  29.  Stommel's Law (p.62):
  30.   ⸺⸺⸺⸺⸺⸺⸺⸺
  31. "Particles that are not at rest in the rotating reference frame...
  32.   find themselves acted upon by the Coriolis forces."
  33.  
  34.  ...and, unfortunately, by diagonal spring forces.
  35.  
  36.  
  37.  Taylor p. 348
  38.  
  39.  Fcor = 2mṙ ⨯ 𝛚  = 2mv ⨯ 𝛚
  40.  
  41.  "...where v is object's velocity relative to the rotating frame"
  42.  
  43. '''
  44.  
  45. def main():
  46.  
  47.     scene.width, scene.height = 1000, 800
  48.     scene.background = color.black
  49.     scene.align = 'left'
  50.     scene.autoscale = False
  51.     scene.autozoom = False
  52.     scene.userspin = False
  53.     scene.userzoom = False
  54.     scene.userpan = False
  55.     scene.resizable = False
  56.     scene.center = vec(0.0, 0.0, 0.0)
  57.     scene.range = 1.3
  58.  
  59.     PAUSED_AT_START = False
  60.  
  61.     RATE = 100
  62.  
  63.     DELTA_T = 0.01
  64.  
  65.     SPRING_CONSTANT = 2000
  66.  
  67.     GRAPH = False
  68.  
  69.     DIAGONAL_SPRINGS = False
  70.  
  71.     APPARENT_VELOCITIES = 0
  72.  
  73.     # SET THE "ROTATING REFERENCE FRAME"
  74.     # TODO: SHOULD THE ROTATING REFERENCE FRAME BE UNPERTURBED?
  75.  
  76.     SELECT = 4
  77.  
  78.     if SELECT == 1:
  79.         BANNER = '(1) Rotate about X with Y and Z perturbation'
  80.         𝛀 = vec(3.0, 0.01, 0.01)
  81.  
  82.     if SELECT == 2:
  83.         BANNER = '(2) Rotate about X with no perturbation'
  84.         𝛀 = vec(3.0, 0.0, 0.0)
  85.  
  86.     if SELECT == 3:
  87.         BANNER = '(3) Rotate about Y with X and Z perturbation'
  88.         𝛀 = vec(0.01, 3.0, 0.01)
  89.  
  90.     if SELECT == 4:
  91.         BANNER = '(4) Rotate about Z with X and Y perturbation'
  92.         𝛀 = vec(0.01, 0.01, 3.0)
  93.  
  94.  
  95.     '''
  96.         A       D
  97.          ⬤─────⬤
  98.          │     │
  99.          │     │
  100.          │     │
  101.          │     │
  102.          ⬤─────⬤
  103.         B       C
  104.    '''
  105.  
  106.     posA = vec(-0.3, 0.0,  0.5)
  107.     posB = vec(-0.3, 0.0, -0.5)
  108.     posC = vec( 0.3, 0.0, -0.5)
  109.     posD = vec( 0.3, 0.0,  0.5)
  110.  
  111.     mA = 0.4
  112.     mB = 0.4
  113.     mC = 0.4
  114.     mD = 0.4
  115.  
  116.     # Center of Mass: CM = ∑mr / M  # Taylor p. 87
  117.     M = mA + mB + mC + mD
  118.     CM = (mA*posA + mB*posB + mC*posC + mD*posD) / M
  119.     # ~ label(text='CM', pos=CM, height=14, box=0, opacity=0)
  120.  
  121.  
  122.     pointA = Sphere(pos=posA, color=color.green)
  123.     pointB = Sphere(pos=posB, color=color.red)
  124.     pointC = Sphere(pos=posC, color=color.blue)
  125.     pointD = Sphere(pos=posD, color=color.yellow)
  126.  
  127.     pointA.m, pointB.m, pointC.m, pointD.m = mA, mB, mC, mD
  128.  
  129.     pointA.lastpos = vec(pointA.pos)
  130.     pointB.lastpos = vec(pointB.pos)
  131.     pointC.lastpos = vec(pointC.pos)
  132.     pointD.lastpos = vec(pointD.pos)
  133.  
  134.     # VELOCITIES
  135.     # v = 𝝎  ⨯ r   # Taylor p. 338
  136.     pointA.v = cross(𝛀, pointA.pos)
  137.     pointB.v = cross(𝛀, pointB.pos)
  138.     pointC.v = cross(𝛀, pointC.pos)
  139.     pointD.v = cross(𝛀, pointD.pos)
  140.  
  141.     # MOMENTA
  142.     # p = mv   # Taylor p. 14
  143.     pointA.p = pointA.m * pointA.v
  144.     pointB.p = pointB.m * pointB.v
  145.     pointC.p = pointC.m * pointC.v
  146.     pointD.p = pointD.m * pointD.v
  147.  
  148.     # THE VECTORS
  149.     rAB = pointB.pos - pointA.pos
  150.     rBC = pointC.pos - pointB.pos
  151.     rCD = pointD.pos - pointC.pos
  152.     rDA = pointA.pos - pointD.pos
  153.     rBD = pointD.pos - pointB.pos
  154.     rAC = pointC.pos - pointA.pos
  155.  
  156.     # THE SPRINGS
  157.     spring1 = Spring(pos=posA, axis=rAB)
  158.     spring2 = Spring(pos=posB, axis=rBC)
  159.     spring3 = Spring(pos=posC, axis=rCD)
  160.     spring4 = Spring(pos=posD, axis=rDA)
  161.     if DIAGONAL_SPRINGS:
  162.         spring5 = Spring(pos=posC, axis=rBD)
  163.         spring6 = Spring(pos=posD, axis=rAC)
  164.  
  165.  
  166.     # THE "RELAXED" LENGTH OF THE SPRINGS (L0)
  167.     rAB_L0 = rAB.mag
  168.     rBC_L0 = rBC.mag
  169.     rCD_L0 = rCD.mag
  170.     rDA_L0 = rDA.mag
  171.     rBD_L0 = rBD.mag
  172.     rAC_L0 = rAC.mag
  173.  
  174.  
  175.     def pause_run():
  176.         g.paused = not g.paused
  177.         pause.text='  RUN   ' if g.paused else 'PAUSE'
  178.     scene.append_to_caption(' '*25)
  179.     pause = button(bind=pause_run)
  180.     g.paused = not PAUSED_AT_START
  181.     pause_run()
  182.  
  183.  
  184.     def key(e):
  185.         # can't use space or enter because focus of button changes
  186.         if e.key in ['p', 'P']: pause_run()
  187.     scene.bind('keydown', key)
  188.  
  189.  
  190.     TEXT = 'CENTRIFUGAL FORCE ACTIVE'
  191.     t1 = Label(text=TEXT, pos=vec(-0.6, -1.1, 0))
  192.     g.centrifugal_flag = True
  193.     def centrifugal_button():
  194.         g.centrifugal_flag = not g.centrifugal_flag
  195.         t1.visible = g.centrifugal_flag
  196.     scene.append_to_caption(' '*5)
  197.     centrifugal_button()
  198.     text = 'CENTRIFUGAL FORCE'
  199.     button(text=text, bind=centrifugal_button)
  200.  
  201.     TEXT = 'CORIOLIS FORCE ACTIVE'
  202.     t2 = Label(text=TEXT, pos=vec(0.6, -1.1, 0))
  203.     g.coriolis_flag = True
  204.     def coriolis_button():
  205.         g.coriolis_flag = not g.coriolis_flag
  206.         t2.visible = g.coriolis_flag
  207.     scene.append_to_caption(' '*5)
  208.     coriolis_button()
  209.     button(text='CORIOLIS FORCE', bind=coriolis_button)
  210.  
  211.  
  212.     def both():
  213.         if not (g.coriolis_flag and g.centrifugal_flag):
  214.             g.centrifugal_flag, g.coriolis_flag = 1, 1
  215.             t1.visible, t2.visible = 1, 1
  216.         else:
  217.             g.centrifugal_flag, g.coriolis_flag = 0, 0
  218.             t1.visible, t2.visible = 0, 0
  219.     scene.append_to_caption(' '*5)
  220.     button(text='TOGGLE BOTH FORCES', bind=both)
  221.  
  222.  
  223.     if GRAPH:
  224.         scene.append_to_caption('\n'*4)
  225.         TEXT = ''
  226.         GRAPH = graph(width=800, height=500, align='right',
  227.                       xmin = 0., xmax = 1.,
  228.                       ymin = 0., ymax = 8.,
  229.                       title=TEXT, )
  230.         pen1 = gcurve(color=color.red)
  231.         pen2 = gcurve(color=color.green)
  232.         pen3 = gcurve(color=color.blue)
  233.  
  234.     t3 = Label(text=BANNER, pos=vec(0.0, 1.1, 0.0), height=26)
  235.  
  236.     t = 0.0
  237.     dt = DELTA_T
  238.     k = SPRING_CONSTANT
  239.  
  240.     while 1:
  241.  
  242.         rate(RATE)
  243.  
  244.         if g.paused: continue
  245.  
  246.         # UPDATE VECTORS
  247.         rAB = pointB.pos - pointA.pos
  248.         rBC = pointC.pos - pointB.pos
  249.         rCD = pointD.pos - pointC.pos
  250.         rDA = pointA.pos - pointD.pos
  251.         rBD = pointD.pos - pointB.pos
  252.         rAC = pointC.pos - pointA.pos
  253.  
  254.         # UPDATE SPRING FORCE OF ONE MASS ON ANOTHER
  255.         # Fs = -k * (‖L‖ - L0) * L.hat
  256.         FAB = -k * (rAB.mag - rAB_L0) * rAB.hat
  257.         FBC = -k * (rBC.mag - rBC_L0) * rBC.hat
  258.         FCD = -k * (rCD.mag - rCD_L0) * rCD.hat
  259.         FDA = -k * (rDA.mag - rDA_L0) * rDA.hat
  260.         if DIAGONAL_SPRINGS:
  261.             FBD = -k * (rBD.mag - rBD_L0) * rBD.hat
  262.             FAC = -k * (rAC.mag - rAC_L0) * rAC.hat
  263.  
  264.         # SUMMATION OF FORCES ON EACH MASS
  265.         if DIAGONAL_SPRINGS:
  266.             FA = FDA - FAB - FAC
  267.             FB = FAB - FBC - FBD
  268.             FC = FBC - FCD + FAC
  269.             FD = FCD - FDA + FBD
  270.         else:
  271.             FA = FDA - FAB
  272.             FB = FAB - FBC
  273.             FC = FBC - FCD
  274.             FD = FCD - FDA
  275.  
  276.         # UPDATE VELOCITIES
  277.         # v = 𝝎  ⨯ r   # Taylor p. 338
  278.         pointA.v.value = cross(𝛀, pointA.pos)
  279.         pointB.v.value = cross(𝛀, pointB.pos)
  280.         pointC.v.value = cross(𝛀, pointC.pos)
  281.         pointD.v.value = cross(𝛀, pointD.pos)
  282.  
  283.         if g.coriolis_flag:
  284.  
  285.             if APPARENT_VELOCITIES:
  286.                 Av = (pointA.pos - pointA.lastpos) / dt
  287.                 Bv = (pointB.pos - pointB.lastpos) / dt
  288.                 Cv = (pointC.pos - pointC.lastpos) / dt
  289.                 Dv = (pointD.pos - pointD.lastpos) / dt
  290.  
  291.                 FA += Coriolis_force(pointA.m, 𝛀, Av)
  292.                 FB += Coriolis_force(pointB.m, 𝛀, Bv)
  293.                 FC += Coriolis_force(pointC.m, 𝛀, Cv)
  294.                 FD += Coriolis_force(pointD.m, 𝛀, Dv)
  295.             else:
  296.                 # Fcor = 2mṙ ⨯ 𝛀  = 2mv ⨯ 𝛀  # Taylor p. 348
  297.                 FA += Coriolis_force(pointA.m, 𝛀, pointA.v)
  298.                 FB += Coriolis_force(pointB.m, 𝛀, pointB.v)
  299.                 FC += Coriolis_force(pointC.m, 𝛀, pointC.v)
  300.                 FD += Coriolis_force(pointD.m, 𝛀, pointD.v)
  301.  
  302.         if g.centrifugal_flag:
  303.             # Fcf = m(𝛀 ⨯ r) ⨯ 𝛀    # Taylor p. 344
  304.             FA += centrifugal_force(pointA.m, 𝛀, pointA.pos)
  305.             FB += centrifugal_force(pointB.m, 𝛀, pointB.pos)
  306.             FC += centrifugal_force(pointC.m, 𝛀, pointC.pos)
  307.             FD += centrifugal_force(pointD.m, 𝛀, pointD.pos)
  308.  
  309.         # UPDATE MOMENTUM OF THE MASSES
  310.         # p2 = p1 + Fnet * 𝛥t
  311.         pointA.p += FA * dt
  312.         pointB.p += FB * dt
  313.         pointC.p += FC * dt
  314.         pointD.p += FD * dt
  315.  
  316.         # RECORD CURRENT POSITIONS
  317.         pointA.lastpos.value = pointA.pos
  318.         pointB.lastpos.value = pointB.pos
  319.         pointC.lastpos.value = pointC.pos
  320.         pointD.lastpos.value = pointD.pos
  321.  
  322.         # UPDATE POSITIONS OF MASSES
  323.         # r2 = r1 + p/m * 𝛥t
  324.         pointA.pos += pointA.p / pointA.m * dt
  325.         pointB.pos += pointB.p / pointB.m * dt
  326.         pointC.pos += pointC.p / pointC.m * dt
  327.         pointD.pos += pointD.p / pointD.m * dt
  328.  
  329.         # UPDATE POSITIONS AND LENGTHS OF SPRINGS
  330.         spring1.pos = pointA.pos; spring1.axis = rAB
  331.         spring2.pos = pointB.pos; spring2.axis = rBC
  332.         spring3.pos = pointC.pos; spring3.axis = rCD
  333.         spring4.pos = pointD.pos; spring4.axis = rDA
  334.         if DIAGONAL_SPRINGS:
  335.             spring5.pos = pointB.pos; spring5.axis = rBD
  336.             spring6.pos = pointA.pos; spring6.axis = rAC
  337.  
  338.         if GRAPH:
  339.             if t > GRAPH.xmax:
  340.                 d = t - GRAPH.xmax
  341.                 GRAPH.xmin += d
  342.                 GRAPH.xmax += d
  343.             # ~ pen1.plot(t, (pointA.pos-pointB.pos).mag)
  344.             # ~ pen1.plot(t, pointA.v.mag)
  345.  
  346.         t += dt
  347.  
  348.  
  349. def centrifugal_force(m, 𝛚, r):
  350.  
  351.     """Find the vector of the centrifugal force given
  352.    mass (m), angular velocity vector (𝛚), and position vector (r).
  353.  
  354.    Fcf = m(𝛚 ⨯ r) ⨯ 𝛚    # Taylor p. 344  """
  355.  
  356.     return m * cross(cross(𝛚, r), 𝛚)
  357.  
  358.  
  359. def Coriolis_force(m, 𝛚, v):
  360.  
  361.     """Find the vector of the Coriolis force given
  362.    mass (m), angular velocity vector (𝛚), and velocity vector (v).
  363.  
  364.    Fcor = 2mṙ ⨯ 𝛚  = 2mv ⨯ 𝛚    # Taylor p. 348  """
  365.  
  366.     return cross(2.0*m*v, 𝛚)
  367.  
  368.  
  369. def angle_between(u, v):
  370.     """
  371. Returns the angle between two numpy vectors in radians (0 to pi)
  372.  
  373. A nugget from Mindless.pdf (section 12), by William Kahan
  374. (https://people.eecs.berkeley.edu/~wkahan/Mindless.pdf)
  375. Used in Brandon Rhodes' Skyfield (functions.py)
  376.  
  377. Prof. W. Kahan:
  378. "Here is a better formula less well known than it deserves:
  379.     ∠(x,y) := 2arctan(‖x.‖y‖ - ‖x‖.y‖/‖x.‖y‖ + ‖x‖.y‖)"
  380.  
  381. ∠(x,y) = 2*atan( ‖( x*‖y‖ - ‖x‖*y )‖
  382.                 -------------------
  383.                 ‖( x*‖y‖ + ‖x‖*y )‖ )
  384.  
  385. ∠(x,y) = 2*atan( mag( x*mag(y) - y*mag(x) )
  386.                 ------------------------
  387.                 mag( x*mag(y) + y*mag(x) ) )
  388.  
  389. ∠(x,y) = 2*atan2(mag(x*mag(y) - y*mag(x)), mag(x*mag(y) + y*mag(x)))
  390.  
  391. notes:
  392. mag(v) = length_of(v) = ‖v‖ = sqrt(v.dot(v)) # v = np.array([x,y,z])
  393. print('\u2016')
  394. print('\u2220')
  395.  
  396.    """
  397.     # from math import atan2, sqrt
  398.  
  399.     a = u * sqrt(v.dot(v))
  400.     b = v * sqrt(u.dot(u))
  401.     y = a - b
  402.     x = a + b
  403.     return 2.0 * atan2( sqrt(y.dot(y)), sqrt(x.dot(x)) )
  404.  
  405.  
  406.  
  407. Sphere = partial(sphere, radius=0.06)
  408.  
  409.  
  410. brass = vec(0.71, 0.65, 0.26)
  411. Spring = partial(helix, radius=.015, coils=45, thickness=.004, color=brass)
  412.  
  413.  
  414. Label = partial(label,
  415.                 height = 22,      # default is 15 pixels
  416.                 box = True,       # default is True
  417.                 visible = True,   # default is True
  418.                 font = 'sans',    # 'sans', 'serif', 'monospace'
  419.                 line = False,     # default is True
  420.                 border = 10,      # default is 5 pixels
  421.                 align = 'center', # 'left', 'right', 'center'
  422.                 opacity = 0.5,    # default is 0.66
  423.                 color = vec(1,1,1),
  424.                 background = vec(.2,.2,.2),
  425.                 )
  426.  
  427. Arrow = partial(arrow, shaftwidth=0.04, color=color.blue)
  428.  
  429.  
  430. class g: ...
  431.  
  432.  
  433. if __name__ == '__main__':
  434.     sys.exit(main())
  435.  
  436.  
  437.  
  438.  
Advertisement
Add Comment
Please, Sign In to add comment