# Copyright (C) 2002-2006 Alexei Gilchrist and Paul Cochrane # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, # USA. # $Id: qi.py,v 1.12 2006/03/03 10:58:40 paultcochrane Exp $ ### = we have the equivalent # = not yet implemented # zero - replaces qubit with |0> state # discard - discard qubit (put "|" vertical bar on qubit wire) # slash - put slash on qubit wire # Utwo - two-qubit operation U # SS - two-qubit gate, symmetric; open squares # ----------------------------------------------------------------------------- ''' Package for drawing quantum circuit diagrams ''' __revision__ = '$Revision: 1.12 $' from pyscript import Rectangle, Color, Circle, Dot, P, Path, TeX, \ Distribute, C, U from pyscript.groups import Group from types import IntType, FloatType, ListType, TupleType, StringType # ------------------------------------------------------------------------- class Boxed(Group, Rectangle): ''' Draws a box around an object, the box can be placed acording to standard Area tags @cvar pad: padding around object @type pad: float @cvar width: overide the width of the box @type width: float @cvar height: override the height of the box @type height: float ''' fg = Color(0) bg = Color(1) pad = 0.2 def __init__(self, obj, **options): Rectangle.__init__(self, **options) Group.__init__(self, **options) bbox = obj.bbox() w = bbox.width+2*self.pad h = bbox.height+2*self.pad self.width = options.get('width', w) self.height = options.get('height', h) self.append( Rectangle(width=self.width, height=self.height, bg=self.bg, fg=self.fg, c=obj.c, r=self.r, linewidth=self.linewidth, dash=self.dash), obj, ) # ------------------------------------------------------------------------- class Circled(Group, Circle): ''' Draws a circle around an object, @cvar pad: padding around object @cvar r: overide the radius of the circle ''' fg = Color(0) bg = Color(1) pad = 0.1 def __init__(self, obj, **options): Circle.__init__(self, **options) Group.__init__(self, **options) bbox = obj.bbox() w = bbox.width+2*self.pad h = bbox.height+2*self.pad self.r = options.get('r', max(w, h)/2.) self.append( Circle(r=self.r, bg=self.bg, fg=self.fg, c=obj.c, linewidth=self.linewidth, dash=self.dash), obj, ) # ------------------------------------------------------------------------- class Gate(Group): """ Gate class """ control = None target = None dot_r = .1 # target object get set in __init__ targetobj = None controlobj = None def __init__(self, tobj, **options): Group.__init__(self, **options) # XXX should we take a copy??? self.targetobj = tobj.copy() if self.controlobj is None: self.controlobj = Dot(r=self.dot_r) # fix up target and control points if type(self.target) in (type(()), type([])): pass elif isinstance(self.target, P): self.target = [self.target] elif self.target is None: self.target = [P(0, 0)] else: raise ValueError, "don't understand target structure for Gate" if type(self.control) in (type(()), type([])): pass elif isinstance(self.control, P): self.control = [self.control] elif self.control is None: self.control = [] else: raise ValueError, "don't understand control structure for Gate" self._make() def settarget(self, *p): """ Sets the target qubit """ self.target = p self._make() def setcontrol(self, *p): """ Sets the control qubit """ self.control = p self._make() def _make(self): """ Makes the gate """ self.clear() # calc average target point tp = self.target[0] if len(self.target)>1: for tt in self.target[1:]: tp = tp+tt tp = tp/float(len(self.target)) self.targetobj.c = tp #XXX should target adjust height here # add controls for cc in self.control: self.append(Path(tp, cc)) self.controlobj.c = cc self.append(self.controlobj.copy()) self.append(self.targetobj) # ------------------------------------------------------------------------- class GateBoxedTeX(Gate): """ Gate with TeX object enclosed in a Box """ def __init__(self, tex, **options): Gate.__init__(self, Boxed(TeX(tex)) , **options) GBT = GateBoxedTeX # ------------------------------------------------------------------------- class GateCircledTeX(Gate): """ Gate with TeX object enclosed in a Circle """ def __init__(self, tex, **options): Gate.__init__(self, Circled(TeX(tex)) , **options) GCT = GateCircledTeX # ------------------------------------------------------------------------- def H(**options): """ Hadamard gate """ return GBT('$H$', **options) def X(**options): """ X gate """ return GBT('$X$', **options) def Y(**options): """ Y gate """ return GBT('$Y$', **options) def Z(**options): """ Z gate """ return GBT('$Z$', **options) def S(**options): """ @todo: ask Alexei what this gate is """ return GBT('$S$', **options) def T(**options): """ @todo: ask Alexei what this gate is """ return GBT('$T$', **options) def RX(arg, **options): """ @todo: ask Alexei what this gate is """ return GCT('$R_x(%s)$'%arg, **options) def RY(arg, **options): """ @todo: ask Alexei what this gate is """ return GCT('$R_y(%s)$'%arg, **options) def RZ(arg, **options): """ @todo: ask Alexei what this gate is """ return GCT('$R_z(%s)$'%arg, **options) # ------------------------------------------------------------------------- def NOT(**options): """ NOT gate """ r = .2 return Gate( Group(Circle(r=r), Path(P(0, r), P(0, -r)), Path(P(-r, 0), P(r, 0))), **options) # ------------------------------------------------------------------------- def CSIGN(**options): """ Controlled sign gate """ return Gate(Dot(r=Gate.dot_r), **options) ZZ = CSIGN # ------------------------------------------------------------------------- def SWAP(**options): """ Swap gate """ x = Group(Path(P(-.1, .1), P(.1, -.1)), Path(P(-.1, -.1), P(.1, .1))) options['controlobj'] = options.get('controlobj', x) return Gate(x, **options) #return Gate(x, **options) # ------------------------------------------------------------------------- # XXX make this a class! class ClassicalPath: """ A classical path """ pass def classicalpath(*paths): ''' @return: classical path @param paths: 1 or more Path() objects ''' g = Group() for path in paths: g.append(path.copy(linewidth=2, fg=Color(0))) # reuse these paths for path in paths: g.append(path(linewidth=1, fg=Color(1))) return g # ------------------------------------------------------------------------- class NoWire(Group): """ Class representing no wire in diagram """ def __init__(self, **options): Group.__init__(self, **options) def set(self, y, e, w): """ Set the east, west and y postions of the NoWire """ return self class QWire(NoWire): """ Class representing a quantum wire """ fg = Color(0) linewidth = None dash = None def set(self, y, e, w): """ Set the east, west and y postions of the QWire """ path = Path(P(w, y), P(e, y), fg=self.fg, linewidth=self.linewidth, dash=self.dash) self.append(path) return self class CWire(QWire): """ Class representing a classical wire """ def set(self, y, e, w): """ Set the east, west and y postions of the CWire """ path = Path(P(w, y), P(e, y), fg=self.fg, linewidth=self.linewidth, dash=self.dash) self.append(classicalpath(path)) return self class Assemble(Group): """ Class representing the assembled objects in diagram/circuit """ wirespacing = 1 gatespacing = .1 wires = [] hang = .2 starthang = hang endhang = hang def __init__(self, *gates, **options): self.starthang = options.get('hang', self.hang) self.endhang = options.get('hang', self.hang) Group.__init__(self, **options) sequence = list(gates) # parse the list ... wires = [] named = {} basetime = 0 while len(sequence) > 0: # the gate ... gate = sequence.pop(0) # the target ... t = sequence.pop(0) wires.append(t) # optional controls ... if len(sequence) > 0 and \ isinstance(sequence[0], (IntType, FloatType)): c = sequence.pop(0) wires.append(c) elif len(sequence) > 0 and \ isinstance(sequence[0], (TupleType, ListType)): c = sequence.pop(0) wires.extend(c) else: c = None g = self.setgate(gate, t, c) # optional time label ... if len(sequence)>0 and isinstance(sequence[0], StringType): l = sequence.pop(0) if named.has_key(l): # group already exists named[l].append(g) else: # create new named group G = named[l] = Group(g) self.append(G) else: self.append(g) L = 0 for ii in self: L += ii.width+self.gatespacing L -= self.gatespacing # XXX add distribute's options Distribute(self, p1=P(0, 0), p2=P(L, 0)) self.recalc_size() # XXX should check wires are ints # add wires ... x0 = self.w.x-self.starthang x1 = self.e.x+self.endhang if len(self.wires) == 0: for w in range(-min(wires), -max(wires)-1, -1): wire = QWire().set(w*self.wirespacing, x0, x1) self.insert(0, wire) self.wires.append(wire) print self.wires else: #w=-int(min(wires)) w = -1 wirestmp = [] for wire in self.wires: # if it already an instance this will have no effect # otherwise create an instance wire = apply(wire, ()) wire.set(w*self.wirespacing, x0, x1) self.insert(0, wire) wirestmp.append(wire) w -= 1 self.wires = wirestmp def setgate(self, gate, target, control=None): """ Set the gate in the assembly """ # if it already an instance this will have no effect # otherwise create an instance gate = apply(gate) # XXX multi target qubits gate.settarget(P(0, -target)) if isinstance(control, (IntType, FloatType)): gate.setcontrol(P(0, -control)) elif isinstance(control, (TupleType, ListType)): tmp = [] for cc in control: tmp.append(P(0, -cc)) apply(gate.setcontrol, tmp) return gate # ------------------------------------------------------------------------- # misc other items # ------------------------------------------------------------------------- class Meter(Group): """ A meter object as in Mike'n'Ike """ height = .7 width = 1.8*height angle = 45 bg = Color(1) mcolor = Color(.8) def __init__(self, **args): Group.__init__(self, **args) h = self.height w = self.width self.append(Rectangle(width=1.8*h, height=h, bg=self.bg)) p = Path( P(.1, .1), C(0, 0), P(w-.1, .1), P(w-.2, .1), C(0, 0), P(.2, .1), closed=1, bg=self.mcolor, fg=None) self.append(p, Path(P(w/2., .1), U(self.angle, h*.9)), ) # ------------------------------------------------------------------------- class Detector(Group): ''' A D shaped detector, can be given an object to surround ''' height = .8 width = height/2. bg = Color(1) fg = Color(0) pad = .1 def __init__(self, object=None, **options): if object is not None: # use the object's boundingbox when width and height not supplied bb = object.bbox() w = bb.width+2*self.pad h = bb.height+2*self.pad self.width = options.get("width", max(w, self.width)) self.height = options.get("height", max(h, self.height)) Group.__init__(self, **options) if self.width>self.height: p = Path( P(0, 0), P(0, self.height), P(self.width-self.height/2., self.height), C(90, 0), P(self.width, self.height/2.), C(180, 90), P(self.width-self.height/2., 0), closed=1) else: p = Path( P(0, 0), P(0, self.height), C(90, 0), P(self.width, self.height/2.), C(180, 90), closed=1) p(bg=options.get("bg", self.bg), fg=options.get("fg", self.fg)) self.append(p) if object is not None: # object looks better if it's slightly off centre # since one side is curved. pad/3 is about right object.c = P(self.width/2.-self.pad/3., self.height/2.) self.append(object) # vim: expandtab shiftwidth=4: