#!/usr/bin/env python # coding: utf-8 # ## Hypocycloid definition and animation ## # ### Deriving the parametric equations of a hypocycloid ### # On May 11 @fermatslibrary posted a gif file, https://twitter.com/fermatslibrary/status/862659602776805379, illustrating the motion of eight cocircular points. The Fermat's Library followers found it so fascinating that the tweet picked up more than 1000 likes and 800 retweets. Soon after I saw the gif I created a similar Python Plotly animation # although the tweet did not mention how it was generated. @plotlygraphs tweeted a link # to my [Jupyter notebook](http://nbviewer.jupyter.org/github/empet/Math/blob/master/fermat-circle-moving-online.ipynb) presenting the animation code. # # How I succeeded to reproduce it so fast? Here I explain the secret: # # At the first sight you can think that the gif displays an illusory rectiliniar motion of the eight points, but it is a real one. I noticed that the moving points lie on a rolling circle along another circle, and I knew that a fixed point on a rolling circle describes a curve called hypocycloid. In the particular case when the ratio of the two radii is 2 the hypocycloid degenerates to a diameter in the base (fixed) circle. # # In this Jupyter notebook I deduce the parametric equations of a hypoycyloid, animate its construction # and explain why when $R/r=2$ any point on the rolling circle runs a diameter in the base circle. # In[1]: from IPython.display import Image Image(filename='generate-hypocycloid.png') # We refer to the figure in the above cell to explain how we get the parameterization of the hypocycloid generated by a fixed point of a circle of center $O'_0$ and radius r, rolling without slipping along the circle # of center O and radius $R>r$. # # Suppose that initially the hypocycloid generating point, $P$, is located at $(R,0)$. # After the small circle was rolling along the greater circle a length corresponding to an angle of measure, $t$, it reaches the point $P'$ on the circle $C(O'_t, r)$. # # Rolling without slipping means that the length the arc $\stackrel{\frown}{PQ}$ of the greater circle equals the length of the arc $\stackrel{\frown}{P'Q}$ on the smaller one, i.e $Rt=r\omega$, where $\omega$ is the measure of the non-oriented angle $\widehat{P'O'_tQ}$ (i.e. we consider $\omega>0$) . Thus $\omega=(R/r)t$ # # The center $O'_t$ has the coordinates $x=(R-r)\cos(t), (R-r)\sin(t)$. The clockwise parameterization of the circle $C(O'_t,r)$ with respect to the coordinate system $x'O'_ty'$ is as follows: # # $$\begin{array}{llr} # x'(\tau)&=&r\cos(\tau)\\ # y'(\tau)&=&-r\sin(\tau), # \end{array}$$ # $\tau\in[0,2\pi]$. # # Hence the point $P'$ on the hypocycloid has the coordinates: # $x'=r\cos(\omega-t), y'=-r\sin(\omega-t)$, and with respect to $xOy$, the coordinates: # # $x=(R-r)\cos(t)+r\cos(\omega-t), y=(R-r)\sin(t)-r\sin(\omega-t)$. # # Replacing $\omega=(R/r)t$ we get the parameterization of the hypocycloid generated by the initial point $P$: # # $$\begin{array}{lll} # x(t)&=&(R-r)\cos(t)+r\cos(t(R-r)/r)\\ # y(t)&=&(R-r)\sin(t)-r\sin(t(R-r)/r), \quad t\in[0,2\pi] # \end{array}$$ # # If $R/r=2$ the parametric equations of the corresponding hypocycloid are: # # $$\begin{array}{lll} # x(t)&=&2r\cos(t)\\ # y(t)&=&0 # \end{array}$$ # # i.e. the moving point $P$ runs the diameter $y=0$, from the position $(R=2r, 0)$ to $(-R,0)$ when $t\in[0,\pi]$, # and back to $(R,0)$, for $t\in[\pi, 2\pi]$. # # What about the trajectory of any other point, $A$, on the rolling circle that at $t=0$ has the angular coordinate $\varphi$ with respect to the center $O'_0$? # # We show that it is also a diameter in the base circle, referring to the figure in the next cell that is a particularization of # the above figure to the case $R=2r$. # In[2]: Image(filename='hypocycloid-2r.png') # The arbitrary point $A$ on the rolling circle has, for t=0, the coordinates: # $x=r+r\cos(\varphi), y=r\sin(\varphi)$. # # The angle $\widehat{QO'_tP'}=\omega$ is in this case $2t$, and $\widehat{B'O'_tP'}=t$. Since $\widehat{A'O'_tP'}=\varphi$, we get that the position of the fixed point on the smaller circle, after rolling along an arc of length $r(2t-\varphi)$, # is $A'(x(t)=r\cos(t)+r\cos(t-\varphi), y(t)=r\sin(t)-r\sin(t-\varphi))$, with $\varphi$ constant, and $t$ variable in the interval $[\varphi, 2\pi+\varphi]$. # # Let us show that $y(t)/x(t)=$constant for all $t$, i.e. the generating point of the hypocycloid lies on a segment of line (diameter in the base circle): # # $$\displaystyle\frac{y(t)}{x(t)}=\frac{r\sin(t)-r\sin(t-\varphi)}{r\cos(t)+r\cos(t-\varphi)}=\left\{\begin{array}{ll}\tan(\varphi/2)& \mbox{if}\:\: t=\varphi/2\\ # \displaystyle\frac{2\cos(t-\varphi/2)\sin(\varphi/2)}{2\cos(t-\varphi/2)\cos(\varphi/2)}=\tan(\varphi/2)& \mbox{if}\:\: t\neq\varphi/2 \end{array}\right.$$ # # Hence the @fermatslibrary animation, illustrated by a Python Plotly code in my [Jupyter notebook](http://nbviewer.jupyter.org/github/empet/Math/blob/master/fermat-circle-moving-online.ipynb), displays the motion of the eight points placed on the rolling # circle of radius $r=R/2$, along the corresponding diameters in the base circle. # ### Animating the hypocycloid generation ### # In[3]: import numpy as np from numpy import pi, cos, sin import copy # In[4]: import plotly.plotly as py from plotly.grid_objs import Grid, Column import time # Set the layout of the plot: # In[5]: axis=dict(showline=False, zeroline=False, showgrid=False, showticklabels=False, range=[-1.1,1.1], autorange=False, title='' ) layout=dict(title='', font=dict(family='Balto'), autosize=False, width=600, height=600, showlegend=False, xaxis=dict(axis), yaxis=dict(axis), hovermode='closest', shapes=[], updatemenus=[dict(type='buttons', showactive=False, y=1, x=1.2, xanchor='right', yanchor='top', pad=dict(l=10), buttons=[dict(label='Play', method='animate', args=[None, dict(frame=dict(duration=90, redraw=False), transition=dict(duration=0), fromcurrent=True, mode='immediate' )] )] )] ) # Define the base circle: # In[6]: layout['shapes'].append(dict(type= 'circle', layer= 'below', xref= 'x', yref='y', fillcolor= 'rgba(245,245,245, 0.95)', x0= -1.005, y0= -1.005, x1= 1.005, y1= 1.005, line= dict(color= 'rgb(40,40,40)', width=2 ) ) ) # In[7]: def circle(C, rad): #C=center, rad=radius theta=np.linspace(0,1,100) return C[0]+rad*cos(2*pi*theta), C[1]-rad*sin(2*pi*theta) # Prepare data for animation to be uploaded to Plotly cloud: # In[8]: def set_my_columns(R=1.0, ratio=3): #R=the radius of base circle #ratio=R/r, where r=is the radius of the rolling circle r=R/float(ratio) xrol, yrol=circle([R-r, 0], 0) my_columns=[Column(xrol, 'xrol'), Column(yrol, 'yrol')] my_columns.append(Column([R-r, R], 'xrad')) my_columns.append(Column([0,0], 'yrad')) my_columns.append(Column([R], 'xstart')) my_columns.append(Column([0], 'ystart')) a=R-r b=(R-r)/float(r) frames=[] t=np.linspace(0,1,50) xpts=[] ypts=[] for k in range(t.shape[0]): X,Y=circle([a*cos(2*pi*t[k]), a*sin(2*pi*t[k])], r) my_columns.append(Column(X, 'xrcirc{}'.format(k+1))) my_columns.append(Column(Y, 'yrcirc{}'.format(k+1))) #The generator point has the coordinates(xp,yp) xp=a*cos(2*pi*t[k])+r*cos(2*pi*b*t[k]) yp=a*sin(2*pi*t[k])-r*sin(2*pi*b*t[k]) xpts.append(xp) ypts.append(yp) my_columns.append(Column([a*cos(2*pi*t[k]), xp], 'xrad{}'.format(k+1))) my_columns.append(Column([a*sin(2*pi*t[k]), yp], 'yrad{}'.format(k+1))) my_columns.append(Column(copy.deepcopy(xpts), 'xpt{}'.format(k+1))) my_columns.append(Column(copy.deepcopy(ypts), 'ypt{}'.format(k+1))) return t, Grid(my_columns) # In[9]: def set_data(grid): return [dict(xsrc=grid.get_column_reference('xrol'),#rolling circle ysrc= grid.get_column_reference('yrol'), mode='lines', line=dict(width=2, color='blue'), name='', ), dict(xsrc=grid.get_column_reference('xrad'),#radius in the rolling circle ysrc= grid.get_column_reference('yrad'), mode='markers+lines', line=dict(width=1.5, color='blue'), marker=dict(size=4, color='blue'), name=''), dict(xsrc=grid.get_column_reference('xstart'),#starting point on the hypocycloid ysrc= grid.get_column_reference('ystart'), mode='marker+lines', line=dict(width=2, color='red', shape='spline'), name='') ] # Set data for each animation frame: # In[10]: def set_frames(t, grid): return [dict(data=[dict(xsrc=grid.get_column_reference('xrcirc{}'.format(k+1)),#update rolling circ position ysrc=grid.get_column_reference('yrcirc{}'.format(k+1)) ), dict(xsrc=grid.get_column_reference('xrad{}'.format(k+1)),#update the radius ysrc=grid.get_column_reference('yrad{}'.format(k+1))#of generating point ), dict(xsrc=grid.get_column_reference('xpt{}'.format(k+1)),#update hypocycloid arc ysrc=grid.get_column_reference('ypt{}'.format(k+1)) ) ], traces=[0,1,2]) for k in range(t.shape[0]) ] # Animate the generation of a hypocycloid with 3 cusps(i.e. $R/r=3$): # In[30]: py.sign_in('empet', 'my_api_key')#access my Plotly account t, grid=set_my_columns(R=1, ratio=3) py.grid_ops.upload(grid, 'animdata-hypo3'+str(time.time()), auto_open=False)#upload data to Plotly cloud # In[31]: data1=set_data(grid) frames1=set_frames(t, grid) title='Hypocycloid with '+str(3)+' cusps, '+'
generated by a fixed point of a circle rolling inside another circle; R/r=3' layout.update(title=title) fig1=dict(data=data1, layout=layout, frames=frames1) py.icreate_animations(fig1, filename='anim-hypocycl3'+str(time.time())) # Hypocycloid with four cusps (astroid): # In[32]: t, grid=set_my_columns(R=1, ratio=4) py.grid_ops.upload(grid, 'animdata-hypo4'+str(time.time()), auto_open=False)#upload data to Plotly cloud # In[33]: data2=set_data(grid) frames2=set_frames(t, grid) title2='Hypocycloid with '+str(4)+' cusps, '+'
generated by a fixed point of a circle rolling inside another circle; R/r=4' layout.update(title=title2) fig2=dict(data=data2, layout=layout, frames=frames2) py.icreate_animations(fig2, filename='anim-hypocycl4'+str(time.time())) # Degenerate hypocycloid (R/r=2): # In[11]: t, grid=set_my_columns(R=1, ratio=2) py.grid_ops.upload(grid, 'animdata-hypo2'+str(time.time()), auto_open=False)#upload data to Plotly cloud # In[12]: data3=set_data(grid) frames3=set_frames(t, grid) title3='Degenerate Hypocycloid; R/r=2' layout.update(title=title3) fig3=dict(data=data3, layout=layout, frames=frames3) py.icreate_animations(fig3, filename='anim-hypocycl2'+str(time.time())) # In[14]: from IPython.core.display import HTML def css_styling(): styles = open("./custom.css", "r").read() return HTML(styles) css_styling()