{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Plotly interactive visualization of complex valued functions ##"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this Jupyter Notebook we generate via [Plotly](https://plot.ly), an interactive plot of a complex valued function, $f:D\\subset\\mathbb{C}\\to\\mathbb{C}$. A complex function is visualized using a version of the \n",
"[domain coloring method](http://nbviewer.jupyter.org/github/empet/Math/blob/master/DomainColoring.ipynb).\n",
"\n",
"Compared with other types of domain coloring, the Plotly interactive plot is much more informative. It displays for each point $z$ in a rectangular region of the complex plane, \n",
"not only the hsv color associated to $\\arg(f(z)$ (argument of $f(z)$), but also the values $\\arg(f(z)$ and $\\log(|f(z)|)$ (the log modulus). "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First we define a Plotly hsv (hue, saturation, value) colorscale, adapted to the range of the numpy functions, `np.angle`, respectively, `np.arctan2`. \n",
"We plot a Heatmap over a rectangular region in the complex plane, colored via this colorscale, according to\n",
"the values of the $\\arg(f(z))$. Over the Heatmap are plotted a few contour lines of the log modulus $\\log(|f(z)|)$.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import numpy.ma as ma\n",
"from numpy import pi\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib.colors"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def hsv_colorscale(S=1, V=1): \n",
" if S < 0 or S > 1 or V < 0 or V > 1:\n",
" raise ValueError('Parameters S (saturation), V (value, brightness) must be in [0,1]')\n",
" \n",
" argument = np.array([-pi, -5*pi/6, -2*pi/3, -3*pi/6, -pi/3, -pi/6, 0, \n",
" pi/6, pi/3, 3*pi/6, 2*pi/3, 5*pi/6, pi])\n",
" \n",
" H = argument/(2*np.pi)+1\n",
" H = np.mod(H,1)\n",
" Sat = S*np.ones_like(H)\n",
" Val = V*np.ones_like(H)\n",
" \n",
" HSV = np.dstack((H, Sat, Val))\n",
" RGB = matplotlib.colors.hsv_to_rgb(HSV) \n",
" \n",
" colormap = (255* np.squeeze(RGB)).astype(int) \n",
" #Define and return the Plotly hsv colorscale adapted to polar coordinates for complex valued functions\n",
" step_size = 1 / (colormap.shape[0]-1)\n",
" return [[round(k*step_size, 4), f'rgb{tuple(c)}'] for k, c in enumerate(colormap)] "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pl_hsv=hsv_colorscale()\n",
"pl_hsv"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The following two functions compute data needed for visualization:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def evaluate_function(func, re=(-1,1), im=(-1,1), N=100):\n",
" # func is the complex function to be ploted\n",
" #re, im are the interval ends on the real and imaginary axes, defining the rectangular region in the complex plane\n",
" #N gives the number of points in an interval of length 1\n",
" l = re[1]-re[0]\n",
" h = im[1]-im[0]\n",
" resL = int(N*l) #horizontal resolution\n",
" resH = int(N*h) #vertical resolution\n",
" X = np.linspace(re[0], re[1], resL)\n",
" Y = np.linspace(im[0], im[1], resH)\n",
" x, y = np.meshgrid(X,Y)\n",
" z = x+1j*y\n",
" w = func(z)\n",
" argument = np.angle(w)\n",
" modulus = np.absolute(w)\n",
" log_modulus = ma.log(modulus)\n",
" return X,Y, argument, log_modulus"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def get_levels(fmodul, nr=10):\n",
" #define the levels for contour plot of the modulus |f(z)|\n",
" #fmodul is the log modulus of f(z) computed on meshgrid\n",
" #nr= the number of contour lines\n",
" mv = np.nanmin(fmodul)\n",
" Mv = np.nanmax(fmodul)\n",
" size = (Mv-mv)/float(nr)\n",
" return [mv+k*size for k in range(nr+1)]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import plotly.graph_objects as go"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We extract the contour lines from an attribute of the `matplotlib.contour.QuadContourSet` object returned by the `matplotlib.pyplot.contour`.\n",
"\n",
"The function defined in the next cell retrieves the points on the contour lines segments, and defines the corresponding Plotly traces:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def plotly_contour_lines(contour_data):\n",
" #contour_data is a matplotlib.contour.QuadContourSet object returned by plt.contour\n",
" contours=contour_data.allsegs # \n",
" if len(contours)==0:\n",
" raise ValueError('Something wrong hapend in computing contour lines')\n",
" #contours is a list of lists; if contour is a list in contours, its elements are arrays. \n",
" #Each array defines a segment of contour line at some level\n",
" xl = []# list of x coordinates of points on a contour line\n",
" yl = []# y\n",
" #lists of coordinates for contour lines consisting in one point:\n",
" xp = []# \n",
" yp = []\n",
"\n",
" for k,contour in enumerate(contours):\n",
" L = len(contour)\n",
" if L!= 0: # sometimes the list of points at the level np.max(modulus) is empty\n",
" for ar in contour:\n",
" if ar.shape[0] == 1:\n",
" xp += [ar[0,0], None]\n",
" yp += [ar[0,1], None]\n",
" else: \n",
" xl += ar[:,0].tolist()\n",
" yl += ar[:,1].tolist()\n",
" xl.append(None) \n",
" yl.append(None)\n",
"\n",
" lines = go.Scatter(x=xl, \n",
" y=yl, \n",
" mode='lines', \n",
" name='modulus', \n",
" line=dict(width=1,\n",
" color='#a5bab7', \n",
" shape='spline', \n",
" smoothing=1),\n",
" hoverinfo='skip'\n",
" ) \n",
" if len(xp) == 0:\n",
" return lines\n",
" else: \n",
" points = go.Scatter(x=xp, y=yp, mode='markers',\n",
" marker=dict(size=4, color='#a5bab7'), \n",
" hoverinfo='none')\n",
" return lines, points"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Set the layout of the plot:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def set_plot_layout(title, width=500, height=500):\n",
" \n",
" return go.Layout(title_text=title,\n",
" title_x=0.5,\n",
" width=width,\n",
" height=height,\n",
" showlegend=False,\n",
" xaxis_title='Re(z)',\n",
" yaxis_title='Im(z)')\n",
" \n",
" "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Define a function that associates to each point $z$ in a meshgrid, the strings containing the values to be displayed when hovering the mouse over that point: "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def text_to_display(x, y, argum, modulus):\n",
" m, n = argum.shape\n",
" return [['z='+'{:.2f}'.format(x[j])+'+' + '{:.2f}'.format(y[i])+' j'+'
arg(f(z))='+'{:.2f}'.format(argum[i][j])+\\\n",
" '
log(modulus(f(z)))='+'{:.2f}'.format(modulus[i][j]) for j in range(n)] for i in range(m)]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, the function `plotly_plot` calls all above defined functions in order to generate the phase plot \n",
"of a given complex function:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def plotly_plot(f, re=(-1,1), im=(-1,1), N=50, nr=10, title='', width=500, height=500, **kwargs):\n",
" x, y, argument, log_modulus=evaluate_function(f, re=re, im=im, N=N)\n",
" \n",
" levels = get_levels(log_modulus, nr=nr)\n",
" plt.figure(figsize=(0.05,0.05))\n",
" plt.axis('off')\n",
" cp = plt.contour(x,y, log_modulus, levels=levels)\n",
" cl = plotly_contour_lines(cp)\n",
" text = text_to_display(x,y, argument, log_modulus.data)\n",
" tickvals = [-np.pi, -2*np.pi/3, -np.pi/3, 0, np.pi/3, 2*np.pi/3, np.pi]\n",
" \n",
" #define the above values as strings with pi-unicode\n",
" ticktext=['-\\u03c0', '-2\\u03c0/3', '-\\u03c0/3', '0', '\\u03c0/3', '2\\u03c0/3', '\\u03c0']\n",
" \n",
" dom = go.Heatmap(x=x, y=y, z=argument, colorscale=pl_hsv, \n",
" text=text, hoverinfo='text',\n",
" colorbar=dict(thickness=20, tickvals=tickvals, \n",
" ticktext=ticktext, \n",
" title='arg(f(z))'))\n",
" if len(cl) == 2 and isinstance(cl[0], go.Scatter):\n",
" data = [dom, cl[0], cl[1]]\n",
" else: \n",
" data = [dom, cl]\n",
" \n",
" layout = set_plot_layout(title=title) \n",
" fig=go.Figure(data=data, layout =layout)\n",
" return fig"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As an example we take the function $f(z)=\\sin(z)/(1-cos(z^3))$: "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fig = plotly_plot(lambda z: np.sin(z)/(1-np.cos(z**3)), re=(-2,2), im=(-2,2), \n",
" nr=22, title='$f(z)=\\\\sin(z)/(1-\\\\cos(z^3))$');\n",
"fig.update_layout(xaxis_range=[-2,2], yaxis_range=[-2,2])\n",
"fig.show()"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from IPython.display import IFrame \n",
"url = \"https://chart-studio.plotly.com/~empet/13957\"\n",
"IFrame(url, width=700, height=700)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 2,
"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": 1
}