{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Electromagnetic wave animation ##"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"An electromagnetic wave is a combination of electric and magnetic fields that vibrate together in space and time, in a synchronous way. Suppose that the end points of the electric field vectors describe, during the field oscillation, the wave $E_z=A\\sin(y/\\lambda-\\omega t), x=0$, while the end points of the magnetic field vectors run the wave $E_x=A\\sin(y/\\lambda-\\omega t), z=0$. \n",
"\n",
"To simulate/animate the electromagnetic wave propagation we plot at each mpmemt t in the time interval of simulation, the electric field and the magnetic field vectors."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'4.2.0'"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import plotly\n",
"plotly.__version__"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import plotly.graph_objs as go"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define a function that returns data to draw vectors of a vector field with values in the plane yOz or xOy (i.e. electric or magnetic field vectors, in our case). "
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def rot_matrix(theta):\n",
" return np.array([[np.cos(theta), -np.sin(theta)], \n",
" [np.sin(theta), np.cos(theta)]])\n",
"\n",
"def get_arrows(start, end, arrow_angle, plane=2, fract=0.1):\n",
" \"\"\"\n",
" this i function defines 3d vectors/quivers\n",
" - start -numpy array of x, y, z-coordinates of the arrow starting points; shape (3, m)\n",
" start[0,:] contains x-coordinates, etc\n",
" - end - numpy array with the same shape as start; contains on rows the x, y and z-coords \n",
" of ending points of the arrow \n",
" - the arrowhead is an isosceles triangle with the equal sides forming an angle of 2*arrow_angle radians \n",
" - plane=0 or 2 depending on the plane where the vectors are drawn (plane=0 i.e. x=0, plane=2, z=0)\n",
" \"\"\"\n",
" start = np.asarray(start)\n",
" end = np.asarray(end)\n",
" m = start[0,:].shape[0]\n",
" arr_dir = start-end\n",
" arr_dir_norm = np.linalg.norm(arr_dir, axis=0)\n",
" arr_dir = fract*arr_dir/arr_dir_norm[None,:] # the arrowhead is a fraction fract from the unit vector\n",
" if plane == 2: \n",
" v = np.einsum('ji, im -> jm', rot_matrix(arrow_angle), arr_dir[:plane,:]) # Einstein summation # rotation to all vectors at a time\n",
" w = np.einsum('ji, im -> jm', rot_matrix(-arrow_angle), arr_dir[:plane, :])\n",
" v = np.append(v, [[0]*m], axis=0) \n",
" w = np.append(w, [[0]*m], axis=0) \n",
" elif plane == 0:\n",
" v = np.einsum('ji, im -> jm', rot_matrix(arrow_angle), arr_dir[1:,:]) \n",
" w = np.einsum('ji, im -> jm', rot_matrix(-arrow_angle), arr_dir[1:, :])\n",
" v = np.append([[0]*m], v, axis=0)\n",
" w = np.append([[0]*m], w, axis=0)\n",
" else: raise ValueError('the key plane must be 0 or 2') \n",
" p = end+v\n",
" q = end+w\n",
"\n",
" suppx = np.stack((start[0,:], end[0,:], np.nan*np.ones(m ))) #supp is the line segment as support for arrow \n",
" suppy = np.stack((start[1,:], end[1,:], np.nan*np.ones(m )))\n",
" suppz = np.stack((start[2,:], end[2,:], np.nan*np.ones(m )))\n",
" x = suppx.flatten('F')#Fortran type flattening\n",
" y = suppy.flatten('F')\n",
" z = suppz.flatten('F')\n",
" x = list(map(lambda u: None if np.isnan(u) else u, x))\n",
" y = list(map(lambda u: None if np.isnan(u) else u, y))\n",
" z = list(map(lambda u: None if np.isnan(u) else u, z))\n",
" \n",
" #headx, heady, headz are the x, y, z coordinates of the triangle vertices\n",
" headx = np.stack((end[0,:], p[0,:], q[0,:], end[0,:], np.nan*np.ones(m)))\n",
" heady = np.stack((end[1,:], p[1,:], q[1,:], end[1,:], np.nan*np.ones(m)))\n",
" headz = np.stack((end[2,:], p[2,:], q[2,:], end[2,:], np.nan*np.ones(m))) \n",
" xx = headx.flatten('F')\n",
" yy = heady.flatten('F')\n",
" zz = headz.flatten('F') \n",
" xx = list(map(lambda u: None if np.isnan(u) else u, xx))\n",
" yy = list(map(lambda u: None if np.isnan(u) else u, yy))\n",
" zz = list(map(lambda u: None if np.isnan(u) else u, zz)) \n",
" \n",
" return x, y, z, xx, yy, zz\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define data for fixed traces in each frame (tr0, tr1, tr2, described below), representing the two orthogonal planes of the electric and magnetic field, and their common line:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"a = 2\n",
"b = 5\n",
" \n",
"xblue = [-a, a, a , -a, -a]\n",
"yblue = [-b, -b, b, b, -b]\n",
"zblue = [0]*5\n",
"\n",
"xred = [0]*5+[None, 0, 0, 0]\n",
"yred = [-b, -b, b, b, -b, None, -b, b]\n",
"zred = [a, -a, -a, a, a, None, 0, 0]\n",
"\n",
"x_Oy = [0, 0]\n",
"y_Oy = [-b, b]\n",
"z_Oy = [0, 0]\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Set the wave parameters and the interval of simulation:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"A = 1 # wave amplitude\n",
"lambdA = 0.5 # wavelength\n",
"omega = 1 # angular frequency\n",
"t = np.arange(0., 10., 0.2)# interval of simulation\n",
"Y = np.arange(-b, b, 0.2) # a grid of an interval on Oy, where the vector fields are evaluated\n",
"X = np.zeros(Y.shape[0])\n",
"ZZe = np.zeros(Y.shape[0])"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(3, 50)"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"nr_frames = t.shape[0]\n",
"theta = np.pi/13 # the characteristic angle of each arrow\n",
"start = np.stack((X, Y, np.zeros(X.shape))) # the numpy array of starting points of the the two classes of vectors\n",
"start.shape"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define data representing the vectors of the two vector fields:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"Ze = A*np.sin(Y/lambdA-omega*t[0])\n",
"end1 = np.stack((X, Y, Ze))\n",
"x1, y1, z1, xx1, yy1, zz1 = get_arrows(start, end1, theta, plane=0)\n",
"XXe = A*np.sin(Y/lambdA-omega*t[0])\n",
"end2 = np.stack((XXe, Y, ZZe))\n",
"x2, y2, z2, xx2, yy2, zz2 = get_arrows(start, end2, theta, plane=2)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"tr0 = dict(type='scatter3d', # a rectangle in xOy \n",
" x=xblue,\n",
" y=yblue,\n",
" z=zblue, \n",
" mode='lines',\n",
" line=dict(width=1.5, color='blue'))\n",
"tr1 = dict(type='scatter3d',# a rectangle in yOz\n",
" x=xred,\n",
" y=yred,\n",
" z=zred, \n",
" mode='lines',\n",
" line=dict(width=1.5, color='red'))\n",
"tr2 = dict(type='scatter3d',#line of direction Oy\n",
" x=x_Oy,\n",
" y=y_Oy,\n",
" z=z_Oy, \n",
" mode='lines',\n",
" line=dict(width=1.5, color='rgb(140,140,140)')) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The following four traces are the base traces updated by the animation frames:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"tr3 = dict(\n",
" type='scatter3d',\n",
" x=x1,\n",
" y=y1,\n",
" z=z1,\n",
" mode='lines', \n",
" line=dict(color='red', width=1.5),\n",
" name=''\n",
" )\n",
" \n",
"tr4 = dict(\n",
" type='scatter3d',\n",
" x=xx1,\n",
" y=yy1,\n",
" z=zz1, \n",
" mode='lines', \n",
" line=dict(color='red', width=2), \n",
" name='' \n",
" )\n",
"tr5 = dict(\n",
" type='scatter3d',\n",
" x=x2,\n",
" y=y2,\n",
" z=z2,\n",
" mode='lines', \n",
" line=dict(color='blue', width=1.5),\n",
" name=''\n",
" )\n",
"tr6 = dict(\n",
" type='scatter3d',\n",
" x=xx2,\n",
" y=yy2,\n",
" z=zz2, \n",
" mode='lines', \n",
" line=dict(color='blue', width=2), \n",
" name=''\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define data to be plotted in each animation frame:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"data = [tr0, tr1, tr2, tr3, tr4, tr5, tr6]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"frames=[]\n",
"for k in range(nr_frames):\n",
" Ze = A*np.sin(Y/lambdA-omega*t[k])\n",
" end1 = np.stack((X, Y, Ze))\n",
" x1, y1, z1, xx1, yy1, zz1 = get_arrows(start, end1, theta, plane=0)\n",
" XXe = A*np.sin(Y/lambdA-omega*t[k])\n",
" end2 = np.stack((XXe, Y, ZZe))\n",
" x2, y2, z2, xx2, yy2, zz2 = get_arrows(start, end2, theta, plane=2)\n",
" frames += [dict(data=[dict(type='scatter3d', \n",
" x=x1,\n",
" y=y1,\n",
" z=z1),\n",
" \n",
" dict(type='scatter3d',\n",
" x=xx1,\n",
" y=yy1,\n",
" z=zz1),\n",
" dict(type='scatter3d',\n",
" x=x2, \n",
" y=y2, \n",
" z=z2),\n",
" dict(type='scatter3d',\n",
" x=xx2,\n",
" y=yy2,\n",
" z=zz2)],\n",
" traces=[3, 4, 5, 6]\n",
" )]\n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Set the plot layout:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"title='Electromagnetic wave propagating in the positive Oy direction
'+\\\n",
"'The electric field vectors (red) are included in the yz-plane,
and the magnetic field vectors (blue), in xy'\n",
"layout = dict(title=title,\n",
" font=dict(family='Balto'),\n",
" autosize=False,\n",
" width=700,\n",
" height=700,\n",
" showlegend=False,\n",
" scene=dict(camera = dict(eye=dict(x=1.22, y=0.55, z=0.3)),\n",
" aspectratio=dict(x=1, y=1, z=0.65),\n",
" xaxis_visible=False,\n",
" yaxis_visible=False, \n",
" zaxis_visible=False,\n",
" ),\n",
" updatemenus=[dict(type='buttons', showactive=False,\n",
" y=0.75,\n",
" x=1.05,\n",
" xanchor='left',\n",
" yanchor='top',\n",
" pad=dict(t=0, l=10),\n",
" buttons=[dict(label='Play',\n",
" method='animate',\n",
" args=[None, \n",
" dict(frame=dict(duration=80, \n",
" redraw=True),\n",
" transition=dict(duration=0),\n",
" fromcurrent=True,\n",
" mode='immediate'\n",
" )\n",
" ]\n",
" )\n",
" ]\n",
" )\n",
" ] \n",
" )\n"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import chart_studio.plotly as py\n",
"fig = go.Figure(data=data, layout=layout, frames=frames)\n",
"py.iplot(fig, filename='anim-electromagwave')"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from IPython.core.display import HTML\n",
"def css_styling():\n",
" styles = open(\"./custom.css\", \"r\").read()\n",
" return HTML(styles)\n",
"css_styling()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}