Navigation

Source code for sympy.physics.quantum.circuitplot

"""Matplotlib based plotting of quantum circuits.Todo:* Optimize printing of large circuits.* Get this to work with single gates.* Do a better job checking the form of circuits to make sure it is a Mul of Gates.* Get multi-target gates plotting.* Get initial and final states to plot.* Get measurements to plot. Might need to rethink measurement as a gate issue.* Get scale and figsize to be handled in a better way.* Write some tests/examples!"""from__future__importprint_function,divisionfromsympyimportMulfromsympy.core.compatibilityimportufromsympy.externalimportimport_modulefromsympy.physics.quantum.gateimportGate,OneQubitGate,CGate,CGateSfromsympy.core.coreimportBasicMetafromsympy.core.assumptionsimportManagedProperties__all__=['CircuitPlot','circuit_plot','labeller','Mz','Mx','CreateOneQubitGate','CreateCGate',]np=import_module('numpy')matplotlib=import_module('matplotlib',__import__kwargs={'fromlist':['pyplot']},catch=(RuntimeError,))# This is raised in environments that have no display.ifnotnpornotmatplotlib:classCircuitPlot(object):def__init__(*args,**kwargs):raiseImportError('numpy or matplotlib not available.')defcircuit_plot(*args,**kwargs):raiseImportError('numpy or matplotlib not available.')else:pyplot=matplotlib.pyplotLine2D=matplotlib.lines.Line2DCircle=matplotlib.patches.Circle#from matplotlib import rc#rc('text',usetex=True)classCircuitPlot(object):"""A class for managing a circuit plot."""scale=1.0fontsize=20.0linewidth=1.0control_radius=0.05not_radius=0.15swap_delta=0.05labels=[]inits={}label_buffer=0.5def__init__(self,c,nqubits,**kwargs):self.circuit=cself.ngates=len(self.circuit.args)self.nqubits=nqubitsself.update(kwargs)self._create_grid()self._create_figure()self._plot_wires()self._plot_gates()self._finish()defupdate(self,kwargs):"""Load the kwargs into the instance dict."""self.__dict__.update(kwargs)def_create_grid(self):"""Create the grid of wires."""scale=self.scalewire_grid=np.arange(0.0,self.nqubits*scale,scale,dtype=float)gate_grid=np.arange(0.0,self.ngates*scale,scale,dtype=float)self._wire_grid=wire_gridself._gate_grid=gate_griddef_create_figure(self):"""Create the main matplotlib figure."""self._figure=pyplot.figure(figsize=(self.ngates*self.scale,self.nqubits*self.scale),facecolor='w',edgecolor='w')ax=self._figure.add_subplot(1,1,1,frameon=True)ax.set_axis_off()offset=0.5*self.scaleax.set_xlim(self._gate_grid[0]-offset,self._gate_grid[-1]+offset)ax.set_ylim(self._wire_grid[0]-offset,self._wire_grid[-1]+offset)ax.set_aspect('equal')self._axes=axdef_plot_wires(self):"""Plot the wires of the circuit diagram."""xstart=self._gate_grid[0]xstop=self._gate_grid[-1]xdata=(xstart-self.scale,xstop+self.scale)foriinrange(self.nqubits):ydata=(self._wire_grid[i],self._wire_grid[i])line=Line2D(xdata,ydata,color='k',lw=self.linewidth)self._axes.add_line(line)ifself.labels:init_label_buffer=0ifself.inits.get(self.labels[i]):init_label_buffer=0.25self._axes.text(xdata[0]-self.label_buffer-init_label_buffer,ydata[0],render_label(self.labels[i],self.inits),size=self.fontsize,color='k',ha='center',va='center')self._plot_measured_wires()def_plot_measured_wires(self):ismeasured=self._measurements()xstop=self._gate_grid[-1]dy=0.04# amount to shift wires when doubled# Plot doubled wires after they are measuredforiminismeasured:xdata=(self._gate_grid[ismeasured[im]],xstop+self.scale)ydata=(self._wire_grid[im]+dy,self._wire_grid[im]+dy)line=Line2D(xdata,ydata,color='k',lw=self.linewidth)self._axes.add_line(line)# Also double any controlled lines off these wiresfori,ginenumerate(self._gates()):ifisinstance(g,CGate)orisinstance(g,CGateS):wires=g.controls+g.targetsforwireinwires:ifwireinismeasuredand \
self._gate_grid[i]>self._gate_grid[ismeasured[wire]]:ydata=min(wires),max(wires)xdata=self._gate_grid[i]-dy,self._gate_grid[i]-dyline=Line2D(xdata,ydata,color='k',lw=self.linewidth)self._axes.add_line(line)def_gates(self):"""Create a list of all gates in the circuit plot."""gates=[]ifisinstance(self.circuit,Mul):forginreversed(self.circuit.args):ifisinstance(g,Gate):gates.append(g)elifisinstance(self.circuit,Gate):gates.append(self.circuit)returngatesdef_plot_gates(self):"""Iterate through the gates and plot each of them."""fori,gateinenumerate(self._gates()):gate.plot_gate(self,i)def_measurements(self):"""Return a dict {i:j} where i is the index of the wire that has been measured, and j is the gate where the wire is measured. """ismeasured={}fori,ginenumerate(self._gates()):ifgetattr(g,'measurement',False):fortargeting.targets:iftargetinismeasured:ifismeasured[target]>i:ismeasured[target]=ielse:ismeasured[target]=ireturnismeasureddef_finish(self):# Disable clipping to make panning work well for large circuits.foroinself._figure.findobj():o.set_clip_on(False)defone_qubit_box(self,t,gate_idx,wire_idx):"""Draw a box for a single qubit gate."""x=self._gate_grid[gate_idx]y=self._wire_grid[wire_idx]self._axes.text(x,y,t,color='k',ha='center',va='center',bbox=dict(ec='k',fc='w',fill=True,lw=self.linewidth),size=self.fontsize)deftwo_qubit_box(self,t,gate_idx,wire_idx):"""Draw a box for a two qubit gate. Doesn't work yet. """x=self._gate_grid[gate_idx]y=self._wire_grid[wire_idx]+0.5print(self._gate_grid)print(self._wire_grid)obj=self._axes.text(x,y,t,color='k',ha='center',va='center',bbox=dict(ec='k',fc='w',fill=True,lw=self.linewidth),size=self.fontsize)defcontrol_line(self,gate_idx,min_wire,max_wire):"""Draw a vertical control line."""xdata=(self._gate_grid[gate_idx],self._gate_grid[gate_idx])ydata=(self._wire_grid[min_wire],self._wire_grid[max_wire])line=Line2D(xdata,ydata,color='k',lw=self.linewidth)self._axes.add_line(line)defcontrol_point(self,gate_idx,wire_idx):"""Draw a control point."""x=self._gate_grid[gate_idx]y=self._wire_grid[wire_idx]radius=self.control_radiusc=Circle((x,y),radius*self.scale,ec='k',fc='k',fill=True,lw=self.linewidth)self._axes.add_patch(c)defnot_point(self,gate_idx,wire_idx):"""Draw a NOT gates as the circle with plus in the middle."""x=self._gate_grid[gate_idx]y=self._wire_grid[wire_idx]radius=self.not_radiusc=Circle((x,y),radius,ec='k',fc='w',fill=False,lw=self.linewidth)self._axes.add_patch(c)l=Line2D((x,x),(y-radius,y+radius),color='k',lw=self.linewidth)self._axes.add_line(l)defswap_point(self,gate_idx,wire_idx):"""Draw a swap point as a cross."""x=self._gate_grid[gate_idx]y=self._wire_grid[wire_idx]d=self.swap_deltal1=Line2D((x-d,x+d),(y-d,y+d),color='k',lw=self.linewidth)l2=Line2D((x-d,x+d),(y+d,y-d),color='k',lw=self.linewidth)self._axes.add_line(l1)self._axes.add_line(l2)defcircuit_plot(c,nqubits,**kwargs):"""Draw the circuit diagram for the circuit with nqubits. Parameters ========== c : circuit The circuit to plot. Should be a product of Gate instances. nqubits : int The number of qubits to include in the circuit. Must be at least as big as the largest `min_qubits`` of the gates. """returnCircuitPlot(c,nqubits,**kwargs)defrender_label(label,inits={}):"""Slightly more flexible way to render labels. >>> from sympy.physics.quantum.circuitplot import render_label >>> render_label('q0') '$|q0\\\\rangle$' >>> render_label('q0', {'q0':'0'}) '$|q0\\\\rangle=|0\\\\rangle$' """init=inits.get(label)ifinit:returnr'$|%s\rangle=|%s\rangle$'%(label,init)returnr'$|%s\rangle$'%label

[docs]classMz(OneQubitGate):"""Mock-up of a z measurement gate. This is in circuitplot rather than gate.py because it's not a real gate, it just draws one. """measurement=Truegate_name='Mz'gate_name_latex=u('M_z')

[docs]classMx(OneQubitGate):"""Mock-up of an x measurement gate. This is in circuitplot rather than gate.py because it's not a real gate, it just draws one. """measurement=Truegate_name='Mx'gate_name_latex=u('M_x')

[docs]defCreateCGate(name,latexname=None):"""Use a lexical closure to make a controlled gate. """ifnotlatexname:latexname=nameonequbitgate=CreateOneQubitGate(name,latexname)defControlledGate(ctrls,target):returnCGate(tuple(ctrls),onequbitgate(target))returnControlledGate