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]: