{ "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 }