12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295 |
- """An implementation of gates that act on qubits.
- Gates are unitary operators that act on the space of qubits.
- Medium Term Todo:
- * Optimize Gate._apply_operators_Qubit to remove the creation of many
- intermediate Qubit objects.
- * Add commutation relationships to all operators and use this in gate_sort.
- * Fix gate_sort and gate_simp.
- * Get multi-target UGates plotting properly.
- * Get UGate to work with either sympy/numpy matrices and output either
- format. This should also use the matrix slots.
- """
- from itertools import chain
- import random
- from sympy.core.add import Add
- from sympy.core.containers import Tuple
- from sympy.core.mul import Mul
- from sympy.core.numbers import (I, Integer)
- from sympy.core.power import Pow
- from sympy.core.numbers import Number
- from sympy.core.singleton import S as _S
- from sympy.core.sorting import default_sort_key
- from sympy.core.sympify import _sympify
- from sympy.functions.elementary.miscellaneous import sqrt
- from sympy.printing.pretty.stringpict import prettyForm, stringPict
- from sympy.physics.quantum.anticommutator import AntiCommutator
- from sympy.physics.quantum.commutator import Commutator
- from sympy.physics.quantum.qexpr import QuantumError
- from sympy.physics.quantum.hilbert import ComplexSpace
- from sympy.physics.quantum.operator import (UnitaryOperator, Operator,
- HermitianOperator)
- from sympy.physics.quantum.matrixutils import matrix_tensor_product, matrix_eye
- from sympy.physics.quantum.matrixcache import matrix_cache
- from sympy.matrices.matrices import MatrixBase
- from sympy.utilities.iterables import is_sequence
- __all__ = [
- 'Gate',
- 'CGate',
- 'UGate',
- 'OneQubitGate',
- 'TwoQubitGate',
- 'IdentityGate',
- 'HadamardGate',
- 'XGate',
- 'YGate',
- 'ZGate',
- 'TGate',
- 'PhaseGate',
- 'SwapGate',
- 'CNotGate',
- # Aliased gate names
- 'CNOT',
- 'SWAP',
- 'H',
- 'X',
- 'Y',
- 'Z',
- 'T',
- 'S',
- 'Phase',
- 'normalized',
- 'gate_sort',
- 'gate_simp',
- 'random_circuit',
- 'CPHASE',
- 'CGateS',
- ]
- #-----------------------------------------------------------------------------
- # Gate Super-Classes
- #-----------------------------------------------------------------------------
- _normalized = True
- def _max(*args, **kwargs):
- if "key" not in kwargs:
- kwargs["key"] = default_sort_key
- return max(*args, **kwargs)
- def _min(*args, **kwargs):
- if "key" not in kwargs:
- kwargs["key"] = default_sort_key
- return min(*args, **kwargs)
- def normalized(normalize):
- """Set flag controlling normalization of Hadamard gates by 1/sqrt(2).
- This is a global setting that can be used to simplify the look of various
- expressions, by leaving off the leading 1/sqrt(2) of the Hadamard gate.
- Parameters
- ----------
- normalize : bool
- Should the Hadamard gate include the 1/sqrt(2) normalization factor?
- When True, the Hadamard gate will have the 1/sqrt(2). When False, the
- Hadamard gate will not have this factor.
- """
- global _normalized
- _normalized = normalize
- def _validate_targets_controls(tandc):
- tandc = list(tandc)
- # Check for integers
- for bit in tandc:
- if not bit.is_Integer and not bit.is_Symbol:
- raise TypeError('Integer expected, got: %r' % tandc[bit])
- # Detect duplicates
- if len(list(set(tandc))) != len(tandc):
- raise QuantumError(
- 'Target/control qubits in a gate cannot be duplicated'
- )
- class Gate(UnitaryOperator):
- """Non-controlled unitary gate operator that acts on qubits.
- This is a general abstract gate that needs to be subclassed to do anything
- useful.
- Parameters
- ----------
- label : tuple, int
- A list of the target qubits (as ints) that the gate will apply to.
- Examples
- ========
- """
- _label_separator = ','
- gate_name = 'G'
- gate_name_latex = 'G'
- #-------------------------------------------------------------------------
- # Initialization/creation
- #-------------------------------------------------------------------------
- @classmethod
- def _eval_args(cls, args):
- args = Tuple(*UnitaryOperator._eval_args(args))
- _validate_targets_controls(args)
- return args
- @classmethod
- def _eval_hilbert_space(cls, args):
- """This returns the smallest possible Hilbert space."""
- return ComplexSpace(2)**(_max(args) + 1)
- #-------------------------------------------------------------------------
- # Properties
- #-------------------------------------------------------------------------
- @property
- def nqubits(self):
- """The total number of qubits this gate acts on.
- For controlled gate subclasses this includes both target and control
- qubits, so that, for examples the CNOT gate acts on 2 qubits.
- """
- return len(self.targets)
- @property
- def min_qubits(self):
- """The minimum number of qubits this gate needs to act on."""
- return _max(self.targets) + 1
- @property
- def targets(self):
- """A tuple of target qubits."""
- return self.label
- @property
- def gate_name_plot(self):
- return r'$%s$' % self.gate_name_latex
- #-------------------------------------------------------------------------
- # Gate methods
- #-------------------------------------------------------------------------
- def get_target_matrix(self, format='sympy'):
- """The matrix rep. of the target part of the gate.
- Parameters
- ----------
- format : str
- The format string ('sympy','numpy', etc.)
- """
- raise NotImplementedError(
- 'get_target_matrix is not implemented in Gate.')
- #-------------------------------------------------------------------------
- # Apply
- #-------------------------------------------------------------------------
- def _apply_operator_IntQubit(self, qubits, **options):
- """Redirect an apply from IntQubit to Qubit"""
- return self._apply_operator_Qubit(qubits, **options)
- def _apply_operator_Qubit(self, qubits, **options):
- """Apply this gate to a Qubit."""
- # Check number of qubits this gate acts on.
- if qubits.nqubits < self.min_qubits:
- raise QuantumError(
- 'Gate needs a minimum of %r qubits to act on, got: %r' %
- (self.min_qubits, qubits.nqubits)
- )
- # If the controls are not met, just return
- if isinstance(self, CGate):
- if not self.eval_controls(qubits):
- return qubits
- targets = self.targets
- target_matrix = self.get_target_matrix(format='sympy')
- # Find which column of the target matrix this applies to.
- column_index = 0
- n = 1
- for target in targets:
- column_index += n*qubits[target]
- n = n << 1
- column = target_matrix[:, int(column_index)]
- # Now apply each column element to the qubit.
- result = 0
- for index in range(column.rows):
- # TODO: This can be optimized to reduce the number of Qubit
- # creations. We should simply manipulate the raw list of qubit
- # values and then build the new Qubit object once.
- # Make a copy of the incoming qubits.
- new_qubit = qubits.__class__(*qubits.args)
- # Flip the bits that need to be flipped.
- for bit in range(len(targets)):
- if new_qubit[targets[bit]] != (index >> bit) & 1:
- new_qubit = new_qubit.flip(targets[bit])
- # The value in that row and column times the flipped-bit qubit
- # is the result for that part.
- result += column[index]*new_qubit
- return result
- #-------------------------------------------------------------------------
- # Represent
- #-------------------------------------------------------------------------
- def _represent_default_basis(self, **options):
- return self._represent_ZGate(None, **options)
- def _represent_ZGate(self, basis, **options):
- format = options.get('format', 'sympy')
- nqubits = options.get('nqubits', 0)
- if nqubits == 0:
- raise QuantumError(
- 'The number of qubits must be given as nqubits.')
- # Make sure we have enough qubits for the gate.
- if nqubits < self.min_qubits:
- raise QuantumError(
- 'The number of qubits %r is too small for the gate.' % nqubits
- )
- target_matrix = self.get_target_matrix(format)
- targets = self.targets
- if isinstance(self, CGate):
- controls = self.controls
- else:
- controls = []
- m = represent_zbasis(
- controls, targets, target_matrix, nqubits, format
- )
- return m
- #-------------------------------------------------------------------------
- # Print methods
- #-------------------------------------------------------------------------
- def _sympystr(self, printer, *args):
- label = self._print_label(printer, *args)
- return '%s(%s)' % (self.gate_name, label)
- def _pretty(self, printer, *args):
- a = stringPict(self.gate_name)
- b = self._print_label_pretty(printer, *args)
- return self._print_subscript_pretty(a, b)
- def _latex(self, printer, *args):
- label = self._print_label(printer, *args)
- return '%s_{%s}' % (self.gate_name_latex, label)
- def plot_gate(self, axes, gate_idx, gate_grid, wire_grid):
- raise NotImplementedError('plot_gate is not implemented.')
- class CGate(Gate):
- """A general unitary gate with control qubits.
- A general control gate applies a target gate to a set of targets if all
- of the control qubits have a particular values (set by
- ``CGate.control_value``).
- Parameters
- ----------
- label : tuple
- The label in this case has the form (controls, gate), where controls
- is a tuple/list of control qubits (as ints) and gate is a ``Gate``
- instance that is the target operator.
- Examples
- ========
- """
- gate_name = 'C'
- gate_name_latex = 'C'
- # The values this class controls for.
- control_value = _S.One
- simplify_cgate = False
- #-------------------------------------------------------------------------
- # Initialization
- #-------------------------------------------------------------------------
- @classmethod
- def _eval_args(cls, args):
- # _eval_args has the right logic for the controls argument.
- controls = args[0]
- gate = args[1]
- if not is_sequence(controls):
- controls = (controls,)
- controls = UnitaryOperator._eval_args(controls)
- _validate_targets_controls(chain(controls, gate.targets))
- return (Tuple(*controls), gate)
- @classmethod
- def _eval_hilbert_space(cls, args):
- """This returns the smallest possible Hilbert space."""
- return ComplexSpace(2)**_max(_max(args[0]) + 1, args[1].min_qubits)
- #-------------------------------------------------------------------------
- # Properties
- #-------------------------------------------------------------------------
- @property
- def nqubits(self):
- """The total number of qubits this gate acts on.
- For controlled gate subclasses this includes both target and control
- qubits, so that, for examples the CNOT gate acts on 2 qubits.
- """
- return len(self.targets) + len(self.controls)
- @property
- def min_qubits(self):
- """The minimum number of qubits this gate needs to act on."""
- return _max(_max(self.controls), _max(self.targets)) + 1
- @property
- def targets(self):
- """A tuple of target qubits."""
- return self.gate.targets
- @property
- def controls(self):
- """A tuple of control qubits."""
- return tuple(self.label[0])
- @property
- def gate(self):
- """The non-controlled gate that will be applied to the targets."""
- return self.label[1]
- #-------------------------------------------------------------------------
- # Gate methods
- #-------------------------------------------------------------------------
- def get_target_matrix(self, format='sympy'):
- return self.gate.get_target_matrix(format)
- def eval_controls(self, qubit):
- """Return True/False to indicate if the controls are satisfied."""
- return all(qubit[bit] == self.control_value for bit in self.controls)
- def decompose(self, **options):
- """Decompose the controlled gate into CNOT and single qubits gates."""
- if len(self.controls) == 1:
- c = self.controls[0]
- t = self.gate.targets[0]
- if isinstance(self.gate, YGate):
- g1 = PhaseGate(t)
- g2 = CNotGate(c, t)
- g3 = PhaseGate(t)
- g4 = ZGate(t)
- return g1*g2*g3*g4
- if isinstance(self.gate, ZGate):
- g1 = HadamardGate(t)
- g2 = CNotGate(c, t)
- g3 = HadamardGate(t)
- return g1*g2*g3
- else:
- return self
- #-------------------------------------------------------------------------
- # Print methods
- #-------------------------------------------------------------------------
- def _print_label(self, printer, *args):
- controls = self._print_sequence(self.controls, ',', printer, *args)
- gate = printer._print(self.gate, *args)
- return '(%s),%s' % (controls, gate)
- def _pretty(self, printer, *args):
- controls = self._print_sequence_pretty(
- self.controls, ',', printer, *args)
- gate = printer._print(self.gate)
- gate_name = stringPict(self.gate_name)
- first = self._print_subscript_pretty(gate_name, controls)
- gate = self._print_parens_pretty(gate)
- final = prettyForm(*first.right(gate))
- return final
- def _latex(self, printer, *args):
- controls = self._print_sequence(self.controls, ',', printer, *args)
- gate = printer._print(self.gate, *args)
- return r'%s_{%s}{\left(%s\right)}' % \
- (self.gate_name_latex, controls, gate)
- def plot_gate(self, circ_plot, gate_idx):
- """
- Plot the controlled gate. If *simplify_cgate* is true, simplify
- C-X and C-Z gates into their more familiar forms.
- """
- min_wire = int(_min(chain(self.controls, self.targets)))
- max_wire = int(_max(chain(self.controls, self.targets)))
- circ_plot.control_line(gate_idx, min_wire, max_wire)
- for c in self.controls:
- circ_plot.control_point(gate_idx, int(c))
- if self.simplify_cgate:
- if self.gate.gate_name == 'X':
- self.gate.plot_gate_plus(circ_plot, gate_idx)
- elif self.gate.gate_name == 'Z':
- circ_plot.control_point(gate_idx, self.targets[0])
- else:
- self.gate.plot_gate(circ_plot, gate_idx)
- else:
- self.gate.plot_gate(circ_plot, gate_idx)
- #-------------------------------------------------------------------------
- # Miscellaneous
- #-------------------------------------------------------------------------
- def _eval_dagger(self):
- if isinstance(self.gate, HermitianOperator):
- return self
- else:
- return Gate._eval_dagger(self)
- def _eval_inverse(self):
- if isinstance(self.gate, HermitianOperator):
- return self
- else:
- return Gate._eval_inverse(self)
- def _eval_power(self, exp):
- if isinstance(self.gate, HermitianOperator):
- if exp == -1:
- return Gate._eval_power(self, exp)
- elif abs(exp) % 2 == 0:
- return self*(Gate._eval_inverse(self))
- else:
- return self
- else:
- return Gate._eval_power(self, exp)
- class CGateS(CGate):
- """Version of CGate that allows gate simplifications.
- I.e. cnot looks like an oplus, cphase has dots, etc.
- """
- simplify_cgate=True
- class UGate(Gate):
- """General gate specified by a set of targets and a target matrix.
- Parameters
- ----------
- label : tuple
- A tuple of the form (targets, U), where targets is a tuple of the
- target qubits and U is a unitary matrix with dimension of
- len(targets).
- """
- gate_name = 'U'
- gate_name_latex = 'U'
- #-------------------------------------------------------------------------
- # Initialization
- #-------------------------------------------------------------------------
- @classmethod
- def _eval_args(cls, args):
- targets = args[0]
- if not is_sequence(targets):
- targets = (targets,)
- targets = Gate._eval_args(targets)
- _validate_targets_controls(targets)
- mat = args[1]
- if not isinstance(mat, MatrixBase):
- raise TypeError('Matrix expected, got: %r' % mat)
- #make sure this matrix is of a Basic type
- mat = _sympify(mat)
- dim = 2**len(targets)
- if not all(dim == shape for shape in mat.shape):
- raise IndexError(
- 'Number of targets must match the matrix size: %r %r' %
- (targets, mat)
- )
- return (targets, mat)
- @classmethod
- def _eval_hilbert_space(cls, args):
- """This returns the smallest possible Hilbert space."""
- return ComplexSpace(2)**(_max(args[0]) + 1)
- #-------------------------------------------------------------------------
- # Properties
- #-------------------------------------------------------------------------
- @property
- def targets(self):
- """A tuple of target qubits."""
- return tuple(self.label[0])
- #-------------------------------------------------------------------------
- # Gate methods
- #-------------------------------------------------------------------------
- def get_target_matrix(self, format='sympy'):
- """The matrix rep. of the target part of the gate.
- Parameters
- ----------
- format : str
- The format string ('sympy','numpy', etc.)
- """
- return self.label[1]
- #-------------------------------------------------------------------------
- # Print methods
- #-------------------------------------------------------------------------
- def _pretty(self, printer, *args):
- targets = self._print_sequence_pretty(
- self.targets, ',', printer, *args)
- gate_name = stringPict(self.gate_name)
- return self._print_subscript_pretty(gate_name, targets)
- def _latex(self, printer, *args):
- targets = self._print_sequence(self.targets, ',', printer, *args)
- return r'%s_{%s}' % (self.gate_name_latex, targets)
- def plot_gate(self, circ_plot, gate_idx):
- circ_plot.one_qubit_box(
- self.gate_name_plot,
- gate_idx, int(self.targets[0])
- )
- class OneQubitGate(Gate):
- """A single qubit unitary gate base class."""
- nqubits = _S.One
- def plot_gate(self, circ_plot, gate_idx):
- circ_plot.one_qubit_box(
- self.gate_name_plot,
- gate_idx, int(self.targets[0])
- )
- def _eval_commutator(self, other, **hints):
- if isinstance(other, OneQubitGate):
- if self.targets != other.targets or self.__class__ == other.__class__:
- return _S.Zero
- return Operator._eval_commutator(self, other, **hints)
- def _eval_anticommutator(self, other, **hints):
- if isinstance(other, OneQubitGate):
- if self.targets != other.targets or self.__class__ == other.__class__:
- return Integer(2)*self*other
- return Operator._eval_anticommutator(self, other, **hints)
- class TwoQubitGate(Gate):
- """A two qubit unitary gate base class."""
- nqubits = Integer(2)
- #-----------------------------------------------------------------------------
- # Single Qubit Gates
- #-----------------------------------------------------------------------------
- class IdentityGate(OneQubitGate):
- """The single qubit identity gate.
- Parameters
- ----------
- target : int
- The target qubit this gate will apply to.
- Examples
- ========
- """
- gate_name = '1'
- gate_name_latex = '1'
- def get_target_matrix(self, format='sympy'):
- return matrix_cache.get_matrix('eye2', format)
- def _eval_commutator(self, other, **hints):
- return _S.Zero
- def _eval_anticommutator(self, other, **hints):
- return Integer(2)*other
- class HadamardGate(HermitianOperator, OneQubitGate):
- """The single qubit Hadamard gate.
- Parameters
- ----------
- target : int
- The target qubit this gate will apply to.
- Examples
- ========
- >>> from sympy import sqrt
- >>> from sympy.physics.quantum.qubit import Qubit
- >>> from sympy.physics.quantum.gate import HadamardGate
- >>> from sympy.physics.quantum.qapply import qapply
- >>> qapply(HadamardGate(0)*Qubit('1'))
- sqrt(2)*|0>/2 - sqrt(2)*|1>/2
- >>> # Hadamard on bell state, applied on 2 qubits.
- >>> psi = 1/sqrt(2)*(Qubit('00')+Qubit('11'))
- >>> qapply(HadamardGate(0)*HadamardGate(1)*psi)
- sqrt(2)*|00>/2 + sqrt(2)*|11>/2
- """
- gate_name = 'H'
- gate_name_latex = 'H'
- def get_target_matrix(self, format='sympy'):
- if _normalized:
- return matrix_cache.get_matrix('H', format)
- else:
- return matrix_cache.get_matrix('Hsqrt2', format)
- def _eval_commutator_XGate(self, other, **hints):
- return I*sqrt(2)*YGate(self.targets[0])
- def _eval_commutator_YGate(self, other, **hints):
- return I*sqrt(2)*(ZGate(self.targets[0]) - XGate(self.targets[0]))
- def _eval_commutator_ZGate(self, other, **hints):
- return -I*sqrt(2)*YGate(self.targets[0])
- def _eval_anticommutator_XGate(self, other, **hints):
- return sqrt(2)*IdentityGate(self.targets[0])
- def _eval_anticommutator_YGate(self, other, **hints):
- return _S.Zero
- def _eval_anticommutator_ZGate(self, other, **hints):
- return sqrt(2)*IdentityGate(self.targets[0])
- class XGate(HermitianOperator, OneQubitGate):
- """The single qubit X, or NOT, gate.
- Parameters
- ----------
- target : int
- The target qubit this gate will apply to.
- Examples
- ========
- """
- gate_name = 'X'
- gate_name_latex = 'X'
- def get_target_matrix(self, format='sympy'):
- return matrix_cache.get_matrix('X', format)
- def plot_gate(self, circ_plot, gate_idx):
- OneQubitGate.plot_gate(self,circ_plot,gate_idx)
- def plot_gate_plus(self, circ_plot, gate_idx):
- circ_plot.not_point(
- gate_idx, int(self.label[0])
- )
- def _eval_commutator_YGate(self, other, **hints):
- return Integer(2)*I*ZGate(self.targets[0])
- def _eval_anticommutator_XGate(self, other, **hints):
- return Integer(2)*IdentityGate(self.targets[0])
- def _eval_anticommutator_YGate(self, other, **hints):
- return _S.Zero
- def _eval_anticommutator_ZGate(self, other, **hints):
- return _S.Zero
- class YGate(HermitianOperator, OneQubitGate):
- """The single qubit Y gate.
- Parameters
- ----------
- target : int
- The target qubit this gate will apply to.
- Examples
- ========
- """
- gate_name = 'Y'
- gate_name_latex = 'Y'
- def get_target_matrix(self, format='sympy'):
- return matrix_cache.get_matrix('Y', format)
- def _eval_commutator_ZGate(self, other, **hints):
- return Integer(2)*I*XGate(self.targets[0])
- def _eval_anticommutator_YGate(self, other, **hints):
- return Integer(2)*IdentityGate(self.targets[0])
- def _eval_anticommutator_ZGate(self, other, **hints):
- return _S.Zero
- class ZGate(HermitianOperator, OneQubitGate):
- """The single qubit Z gate.
- Parameters
- ----------
- target : int
- The target qubit this gate will apply to.
- Examples
- ========
- """
- gate_name = 'Z'
- gate_name_latex = 'Z'
- def get_target_matrix(self, format='sympy'):
- return matrix_cache.get_matrix('Z', format)
- def _eval_commutator_XGate(self, other, **hints):
- return Integer(2)*I*YGate(self.targets[0])
- def _eval_anticommutator_YGate(self, other, **hints):
- return _S.Zero
- class PhaseGate(OneQubitGate):
- """The single qubit phase, or S, gate.
- This gate rotates the phase of the state by pi/2 if the state is ``|1>`` and
- does nothing if the state is ``|0>``.
- Parameters
- ----------
- target : int
- The target qubit this gate will apply to.
- Examples
- ========
- """
- gate_name = 'S'
- gate_name_latex = 'S'
- def get_target_matrix(self, format='sympy'):
- return matrix_cache.get_matrix('S', format)
- def _eval_commutator_ZGate(self, other, **hints):
- return _S.Zero
- def _eval_commutator_TGate(self, other, **hints):
- return _S.Zero
- class TGate(OneQubitGate):
- """The single qubit pi/8 gate.
- This gate rotates the phase of the state by pi/4 if the state is ``|1>`` and
- does nothing if the state is ``|0>``.
- Parameters
- ----------
- target : int
- The target qubit this gate will apply to.
- Examples
- ========
- """
- gate_name = 'T'
- gate_name_latex = 'T'
- def get_target_matrix(self, format='sympy'):
- return matrix_cache.get_matrix('T', format)
- def _eval_commutator_ZGate(self, other, **hints):
- return _S.Zero
- def _eval_commutator_PhaseGate(self, other, **hints):
- return _S.Zero
- # Aliases for gate names.
- H = HadamardGate
- X = XGate
- Y = YGate
- Z = ZGate
- T = TGate
- Phase = S = PhaseGate
- #-----------------------------------------------------------------------------
- # 2 Qubit Gates
- #-----------------------------------------------------------------------------
- class CNotGate(HermitianOperator, CGate, TwoQubitGate):
- """Two qubit controlled-NOT.
- This gate performs the NOT or X gate on the target qubit if the control
- qubits all have the value 1.
- Parameters
- ----------
- label : tuple
- A tuple of the form (control, target).
- Examples
- ========
- >>> from sympy.physics.quantum.gate import CNOT
- >>> from sympy.physics.quantum.qapply import qapply
- >>> from sympy.physics.quantum.qubit import Qubit
- >>> c = CNOT(1,0)
- >>> qapply(c*Qubit('10')) # note that qubits are indexed from right to left
- |11>
- """
- gate_name = 'CNOT'
- gate_name_latex = r'\text{CNOT}'
- simplify_cgate = True
- #-------------------------------------------------------------------------
- # Initialization
- #-------------------------------------------------------------------------
- @classmethod
- def _eval_args(cls, args):
- args = Gate._eval_args(args)
- return args
- @classmethod
- def _eval_hilbert_space(cls, args):
- """This returns the smallest possible Hilbert space."""
- return ComplexSpace(2)**(_max(args) + 1)
- #-------------------------------------------------------------------------
- # Properties
- #-------------------------------------------------------------------------
- @property
- def min_qubits(self):
- """The minimum number of qubits this gate needs to act on."""
- return _max(self.label) + 1
- @property
- def targets(self):
- """A tuple of target qubits."""
- return (self.label[1],)
- @property
- def controls(self):
- """A tuple of control qubits."""
- return (self.label[0],)
- @property
- def gate(self):
- """The non-controlled gate that will be applied to the targets."""
- return XGate(self.label[1])
- #-------------------------------------------------------------------------
- # Properties
- #-------------------------------------------------------------------------
- # The default printing of Gate works better than those of CGate, so we
- # go around the overridden methods in CGate.
- def _print_label(self, printer, *args):
- return Gate._print_label(self, printer, *args)
- def _pretty(self, printer, *args):
- return Gate._pretty(self, printer, *args)
- def _latex(self, printer, *args):
- return Gate._latex(self, printer, *args)
- #-------------------------------------------------------------------------
- # Commutator/AntiCommutator
- #-------------------------------------------------------------------------
- def _eval_commutator_ZGate(self, other, **hints):
- """[CNOT(i, j), Z(i)] == 0."""
- if self.controls[0] == other.targets[0]:
- return _S.Zero
- else:
- raise NotImplementedError('Commutator not implemented: %r' % other)
- def _eval_commutator_TGate(self, other, **hints):
- """[CNOT(i, j), T(i)] == 0."""
- return self._eval_commutator_ZGate(other, **hints)
- def _eval_commutator_PhaseGate(self, other, **hints):
- """[CNOT(i, j), S(i)] == 0."""
- return self._eval_commutator_ZGate(other, **hints)
- def _eval_commutator_XGate(self, other, **hints):
- """[CNOT(i, j), X(j)] == 0."""
- if self.targets[0] == other.targets[0]:
- return _S.Zero
- else:
- raise NotImplementedError('Commutator not implemented: %r' % other)
- def _eval_commutator_CNotGate(self, other, **hints):
- """[CNOT(i, j), CNOT(i,k)] == 0."""
- if self.controls[0] == other.controls[0]:
- return _S.Zero
- else:
- raise NotImplementedError('Commutator not implemented: %r' % other)
- class SwapGate(TwoQubitGate):
- """Two qubit SWAP gate.
- This gate swap the values of the two qubits.
- Parameters
- ----------
- label : tuple
- A tuple of the form (target1, target2).
- Examples
- ========
- """
- gate_name = 'SWAP'
- gate_name_latex = r'\text{SWAP}'
- def get_target_matrix(self, format='sympy'):
- return matrix_cache.get_matrix('SWAP', format)
- def decompose(self, **options):
- """Decompose the SWAP gate into CNOT gates."""
- i, j = self.targets[0], self.targets[1]
- g1 = CNotGate(i, j)
- g2 = CNotGate(j, i)
- return g1*g2*g1
- def plot_gate(self, circ_plot, gate_idx):
- min_wire = int(_min(self.targets))
- max_wire = int(_max(self.targets))
- circ_plot.control_line(gate_idx, min_wire, max_wire)
- circ_plot.swap_point(gate_idx, min_wire)
- circ_plot.swap_point(gate_idx, max_wire)
- def _represent_ZGate(self, basis, **options):
- """Represent the SWAP gate in the computational basis.
- The following representation is used to compute this:
- SWAP = |1><1|x|1><1| + |0><0|x|0><0| + |1><0|x|0><1| + |0><1|x|1><0|
- """
- format = options.get('format', 'sympy')
- targets = [int(t) for t in self.targets]
- min_target = _min(targets)
- max_target = _max(targets)
- nqubits = options.get('nqubits', self.min_qubits)
- op01 = matrix_cache.get_matrix('op01', format)
- op10 = matrix_cache.get_matrix('op10', format)
- op11 = matrix_cache.get_matrix('op11', format)
- op00 = matrix_cache.get_matrix('op00', format)
- eye2 = matrix_cache.get_matrix('eye2', format)
- result = None
- for i, j in ((op01, op10), (op10, op01), (op00, op00), (op11, op11)):
- product = nqubits*[eye2]
- product[nqubits - min_target - 1] = i
- product[nqubits - max_target - 1] = j
- new_result = matrix_tensor_product(*product)
- if result is None:
- result = new_result
- else:
- result = result + new_result
- return result
- # Aliases for gate names.
- CNOT = CNotGate
- SWAP = SwapGate
- def CPHASE(a,b): return CGateS((a,),Z(b))
- #-----------------------------------------------------------------------------
- # Represent
- #-----------------------------------------------------------------------------
- def represent_zbasis(controls, targets, target_matrix, nqubits, format='sympy'):
- """Represent a gate with controls, targets and target_matrix.
- This function does the low-level work of representing gates as matrices
- in the standard computational basis (ZGate). Currently, we support two
- main cases:
- 1. One target qubit and no control qubits.
- 2. One target qubits and multiple control qubits.
- For the base of multiple controls, we use the following expression [1]:
- 1_{2**n} + (|1><1|)^{(n-1)} x (target-matrix - 1_{2})
- Parameters
- ----------
- controls : list, tuple
- A sequence of control qubits.
- targets : list, tuple
- A sequence of target qubits.
- target_matrix : sympy.Matrix, numpy.matrix, scipy.sparse
- The matrix form of the transformation to be performed on the target
- qubits. The format of this matrix must match that passed into
- the `format` argument.
- nqubits : int
- The total number of qubits used for the representation.
- format : str
- The format of the final matrix ('sympy', 'numpy', 'scipy.sparse').
- Examples
- ========
- References
- ----------
- [1] http://www.johnlapeyre.com/qinf/qinf_html/node6.html.
- """
- controls = [int(x) for x in controls]
- targets = [int(x) for x in targets]
- nqubits = int(nqubits)
- # This checks for the format as well.
- op11 = matrix_cache.get_matrix('op11', format)
- eye2 = matrix_cache.get_matrix('eye2', format)
- # Plain single qubit case
- if len(controls) == 0 and len(targets) == 1:
- product = []
- bit = targets[0]
- # Fill product with [I1,Gate,I2] such that the unitaries,
- # I, cause the gate to be applied to the correct Qubit
- if bit != nqubits - 1:
- product.append(matrix_eye(2**(nqubits - bit - 1), format=format))
- product.append(target_matrix)
- if bit != 0:
- product.append(matrix_eye(2**bit, format=format))
- return matrix_tensor_product(*product)
- # Single target, multiple controls.
- elif len(targets) == 1 and len(controls) >= 1:
- target = targets[0]
- # Build the non-trivial part.
- product2 = []
- for i in range(nqubits):
- product2.append(matrix_eye(2, format=format))
- for control in controls:
- product2[nqubits - 1 - control] = op11
- product2[nqubits - 1 - target] = target_matrix - eye2
- return matrix_eye(2**nqubits, format=format) + \
- matrix_tensor_product(*product2)
- # Multi-target, multi-control is not yet implemented.
- else:
- raise NotImplementedError(
- 'The representation of multi-target, multi-control gates '
- 'is not implemented.'
- )
- #-----------------------------------------------------------------------------
- # Gate manipulation functions.
- #-----------------------------------------------------------------------------
- def gate_simp(circuit):
- """Simplifies gates symbolically
- It first sorts gates using gate_sort. It then applies basic
- simplification rules to the circuit, e.g., XGate**2 = Identity
- """
- # Bubble sort out gates that commute.
- circuit = gate_sort(circuit)
- # Do simplifications by subing a simplification into the first element
- # which can be simplified. We recursively call gate_simp with new circuit
- # as input more simplifications exist.
- if isinstance(circuit, Add):
- return sum(gate_simp(t) for t in circuit.args)
- elif isinstance(circuit, Mul):
- circuit_args = circuit.args
- elif isinstance(circuit, Pow):
- b, e = circuit.as_base_exp()
- circuit_args = (gate_simp(b)**e,)
- else:
- return circuit
- # Iterate through each element in circuit, simplify if possible.
- for i in range(len(circuit_args)):
- # H,X,Y or Z squared is 1.
- # T**2 = S, S**2 = Z
- if isinstance(circuit_args[i], Pow):
- if isinstance(circuit_args[i].base,
- (HadamardGate, XGate, YGate, ZGate)) \
- and isinstance(circuit_args[i].exp, Number):
- # Build a new circuit taking replacing the
- # H,X,Y,Z squared with one.
- newargs = (circuit_args[:i] +
- (circuit_args[i].base**(circuit_args[i].exp % 2),) +
- circuit_args[i + 1:])
- # Recursively simplify the new circuit.
- circuit = gate_simp(Mul(*newargs))
- break
- elif isinstance(circuit_args[i].base, PhaseGate):
- # Build a new circuit taking old circuit but splicing
- # in simplification.
- newargs = circuit_args[:i]
- # Replace PhaseGate**2 with ZGate.
- newargs = newargs + (ZGate(circuit_args[i].base.args[0])**
- (Integer(circuit_args[i].exp/2)), circuit_args[i].base**
- (circuit_args[i].exp % 2))
- # Append the last elements.
- newargs = newargs + circuit_args[i + 1:]
- # Recursively simplify the new circuit.
- circuit = gate_simp(Mul(*newargs))
- break
- elif isinstance(circuit_args[i].base, TGate):
- # Build a new circuit taking all the old elements.
- newargs = circuit_args[:i]
- # Put an Phasegate in place of any TGate**2.
- newargs = newargs + (PhaseGate(circuit_args[i].base.args[0])**
- Integer(circuit_args[i].exp/2), circuit_args[i].base**
- (circuit_args[i].exp % 2))
- # Append the last elements.
- newargs = newargs + circuit_args[i + 1:]
- # Recursively simplify the new circuit.
- circuit = gate_simp(Mul(*newargs))
- break
- return circuit
- def gate_sort(circuit):
- """Sorts the gates while keeping track of commutation relations
- This function uses a bubble sort to rearrange the order of gate
- application. Keeps track of Quantum computations special commutation
- relations (e.g. things that apply to the same Qubit do not commute with
- each other)
- circuit is the Mul of gates that are to be sorted.
- """
- # Make sure we have an Add or Mul.
- if isinstance(circuit, Add):
- return sum(gate_sort(t) for t in circuit.args)
- if isinstance(circuit, Pow):
- return gate_sort(circuit.base)**circuit.exp
- elif isinstance(circuit, Gate):
- return circuit
- if not isinstance(circuit, Mul):
- return circuit
- changes = True
- while changes:
- changes = False
- circ_array = circuit.args
- for i in range(len(circ_array) - 1):
- # Go through each element and switch ones that are in wrong order
- if isinstance(circ_array[i], (Gate, Pow)) and \
- isinstance(circ_array[i + 1], (Gate, Pow)):
- # If we have a Pow object, look at only the base
- first_base, first_exp = circ_array[i].as_base_exp()
- second_base, second_exp = circ_array[i + 1].as_base_exp()
- # Use SymPy's hash based sorting. This is not mathematical
- # sorting, but is rather based on comparing hashes of objects.
- # See Basic.compare for details.
- if first_base.compare(second_base) > 0:
- if Commutator(first_base, second_base).doit() == 0:
- new_args = (circuit.args[:i] + (circuit.args[i + 1],) +
- (circuit.args[i],) + circuit.args[i + 2:])
- circuit = Mul(*new_args)
- changes = True
- break
- if AntiCommutator(first_base, second_base).doit() == 0:
- new_args = (circuit.args[:i] + (circuit.args[i + 1],) +
- (circuit.args[i],) + circuit.args[i + 2:])
- sign = _S.NegativeOne**(first_exp*second_exp)
- circuit = sign*Mul(*new_args)
- changes = True
- break
- return circuit
- #-----------------------------------------------------------------------------
- # Utility functions
- #-----------------------------------------------------------------------------
- def random_circuit(ngates, nqubits, gate_space=(X, Y, Z, S, T, H, CNOT, SWAP)):
- """Return a random circuit of ngates and nqubits.
- This uses an equally weighted sample of (X, Y, Z, S, T, H, CNOT, SWAP)
- gates.
- Parameters
- ----------
- ngates : int
- The number of gates in the circuit.
- nqubits : int
- The number of qubits in the circuit.
- gate_space : tuple
- A tuple of the gate classes that will be used in the circuit.
- Repeating gate classes multiple times in this tuple will increase
- the frequency they appear in the random circuit.
- """
- qubit_space = range(nqubits)
- result = []
- for i in range(ngates):
- g = random.choice(gate_space)
- if g == CNotGate or g == SwapGate:
- qubits = random.sample(qubit_space, 2)
- g = g(*qubits)
- else:
- qubit = random.choice(qubit_space)
- g = g(qubit)
- result.append(g)
- return Mul(*result)
- def zx_basis_transform(self, format='sympy'):
- """Transformation matrix from Z to X basis."""
- return matrix_cache.get_matrix('ZX', format)
- def zy_basis_transform(self, format='sympy'):
- """Transformation matrix from Z to Y basis."""
- return matrix_cache.get_matrix('ZY', format)
|