In this Jupyter Notebook we generate via Plotly, 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 domain coloring method.
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, 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).
First we define a Plotly hsv (hue, saturation, value) colorscale, adapted to the range of the numpy functions, np.angle, respectively, np.arctan2.
We plot a Heatmap over a rectangular region in the complex plane, colored via this colorscale, according to
the values of the $\arg(f(z))$. Over the Heatmap are plotted a few contour lines of the log modulus $\log(|f(z)|)$.
In [1]:
import numpy as np
import numpy.ma as ma
from numpy import pi
import matplotlib.pyplot as plt
import matplotlib.colors
In [2]:
def hsv_colorscale(S=1, V=1):
if S <0 or S>1 or V<0 or V>1:
raise ValueError('Parameters S (saturation), V (value, brightness) must be in [0,1]')
argument=np.array([-pi, -5*pi/6, -2*pi/3, -3*pi/6, -pi/3, -pi/6, 0,
pi/6, pi/3, 3*pi/6, 2*pi/3, 5*pi/6, pi])
H=argument/(2*np.pi)+1
H=np.mod(H,1)
Sat=S*np.ones_like(H)
Val=V*np.ones_like(H)
HSV = np.dstack((H,Sat,Val))
RGB = matplotlib.colors.hsv_to_rgb(HSV)
colormap = 255* np.squeeze(RGB)
#Define and return the Plotly hsv colorscale adapted to polar coordinates for complex valued functions
step_size=1./(colormap.shape[0]-1)
return [[k*step_size, 'rgb'+str((int(c[0]), int(c[1]), int(c[2])))] for k, c in enumerate(colormap)]
In [3]:
pl_hsv=hsv_colorscale(S=1,V=1)
pl_hsv
Out[3]:
The following two functions compute data needed for visualization:
In [4]:
def computations(func, re=(-1,1), im=(-1,1), N=100):
# func is the complex function to be ploted
#re, im are the interval ends on the real and imaginary axes, defining the rectangular region in the complex plane
#N gives the number of points in an interval of length 1
l=re[1]-re[0]
h=im[1]-im[0]
resL=int(N*l) #horizontal resolution
resH=int(N*h) #vertical resolution
X=np.linspace(re[0], re[1], resL)
Y=np.linspace(im[0], im[1], resH)
x, y=np.meshgrid(X,Y)
z=x+1j*y
w=func(z)
argument=np.angle(w)
modulus=np.absolute(w)
log_modulus=ma.log(modulus)
return x,y, argument, log_modulus
In [5]:
def get_levels(fmodul, nr=10):
#define the levels for contour plot of the modulus |f(z)|
#fmodul is the log modulus of f(z) computed on meshgrid
#nr= the number of contour lines
mv=np.nanmin(fmodul)
Mv=np.nanmax(fmodul)
size=(Mv-mv)/float(nr)
return [mv+k*size for k in range(nr+1)]
In [6]:
import plotly.plotly as py
from plotly.graph_objs import *
We extract the contour lines from an attribute of the matplotlib.contour.QuadContourSet object returned by the matplotlib.pyplot.contour.
The function defined in the next cell retrieves the points on the contour lines segments, and defines the corresponding Plotly traces:
In [7]:
def plotly_contour_lines(contour_data):
#contour_data is a matplotlib.contour.QuadContourSet object returned by plt.contour
contours=contour_data.allsegs #
if len(contours)==0:
raise ValueError('Something wrong hapend in computing contour lines')
#contours is a list of lists; if contour is a list in contours, its elements are arrays.
#Each array defines a segment of contour line at some level
xl=[]# list of x coordinates of points on a contour line
yl=[]# y
#lists of coordinates for contour lines consisting in one point:
xp=[]#
yp=[]
for k,contour in enumerate(contours):
L=len(contour)
if L!=0: # sometimes the list of points at the level np.max(modulus) is empty
for ar in contour:
if ar.shape[0]==1:
xp+=[ar[0,0], None]
yp+=[ar[0,1], None]
else:
xl+=ar[:,0].tolist()
yl+=ar[:,1].tolist()
xl.append(None)
yl.append(None)
lines=Scatter(x=xl,
y=yl,
mode='lines',
name='modulus',
line=dict(width=1,
color='#a5bab7',
shape='spline',
smoothing=1),
hoverinfo='none'
)
if len(xp)==0:
return lines
else:
points=Scatter(x=xp, y=yp, mode='markers', marker=dict(size=4, color='#a5bab7'), hoverinfo='none')
return lines, points
Set the layout of the plot:
In [8]:
def set_plot_layout(title, width=500, height=500, showticklabels=True, ticks='outside'):
axis=dict(showgrid=False,
showline=False,
showticklabels=showticklabels,
ticks=ticks,
zeroline=False)
return Layout(title=title,
width=width,
height=height,
showlegend=False,
xaxis=XAxis(axis, title='Re(z)'),
yaxis=YAxis(axis, title='Im(z)'),
)
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:
In [9]:
def text_to_display(x, y, argum, modulus):
m,n=x.shape
return [['z='+'{:.2f}'.format(x[i][j])+'+' + '{:.2f}'.format(y[i][j])+' j'+'<br>arg(f(z))='+'{:.2f}'.format(argum[i][j])+\
'<br>log(modulus(f(z)))='+'{:.2f}'.format(modulus[i][j]) for j in range(n)] for i in range(m)]
Finally, the function plotly_plot calls all above defined functions in order to generate the phase plot
of a given complex function:
In [14]:
def plotly_plot(f, re=(-1,1), im=(-1,1), N=100, nr=10, title='', width=500, height=500, **kwargs):
x, y, argument, log_modulus=computations(f, re=re, im=im, N=N)
levels=get_levels(log_modulus, nr=nr)
cp=plt.contour(x,y, log_modulus, levels=levels)
cl=plotly_contour_lines(cp)
text=text_to_display(x,y, argument, log_modulus.data)
tickvals=[-np.pi, -2*np.pi/3, -np.pi/3, 0, np.pi/3, 2*np.pi/3, np.pi]
ticktext=['-pi', '-2pi/3', '-pi/3', '0', 'pi/3', '2pi/3', 'pi']
dom=Heatmap(x=x[0,:], y=y[:,0], z=argument, colorscale=pl_hsv, showscale=True, text=text, hoverinfo='text',
colorbar=dict(thickness=20, tickvals=tickvals,
ticktext=ticktext, ticks='outside',
ticklen=4, titleside='top', title='arg(f(z))'))
if len(cl)==2 and type(cl[0]) is graph_objs.Scatter:
data=Data([dom, cl[0], cl[1]])
else:
data=Data([dom, cl])
layout=set_plot_layout(title=title)
fig=Figure(data=data, layout =layout)
fig['layout'].update( margin=dict(t=100))
return fig
As an example we take the function $f(z)=\sin(z)/(1-cos(z^3))$:
In [15]:
fig=plotly_plot(lambda z: np.sin(z)/(1-np.cos(z**3)), re=(-2,2), im=(-2,2),
nr=22, title='f(z)=sin(z)/(1-cos(z^3))')
In [17]:
py.sign_in('empet', 'my_api_key')
py.iplot(fig, filename='phase-plot1')
Out[17]:
In [1]:
from IPython.core.display import HTML
def css_styling():
styles = open("./custom.css", "r").read()
return HTML(styles)
css_styling()
Out[1]: