1313# limitations under the License.
1414
1515"""QubitOperator stores a sum of Pauli operators acting on qubits."""
16+ import cmath
1617import copy
1718import itertools
1819
1920import numpy
2021
22+ from ._basics import BasicGate , NotInvertible , NotMergeable
23+ from ._command import apply_command
24+ from ._gates import Ph , X , Y , Z
25+
2126
2227EQ_TOLERANCE = 1e-12
2328
@@ -45,7 +50,7 @@ class QubitOperatorError(Exception):
4550 pass
4651
4752
48- class QubitOperator (object ):
53+ class QubitOperator (BasicGate ):
4954 """
5055 A sum of terms acting on qubits, e.g., 0.5 * 'X0 X5' + 0.3 * 'Z1 Z2'.
5156
@@ -68,6 +73,25 @@ class QubitOperator(object):
6873
6974 hamiltonian = 0.5 * QubitOperator('X0 X5') + 0.3 * QubitOperator('Z0')
7075
76+ Our Simulator takes a hermitian QubitOperator to directly calculate the
77+ expectation value (see Simulator.get_expectation_value) of this observable.
78+
79+ A hermitian QubitOperator can also be used as input for the
80+ TimeEvolution gate.
81+
82+ If the QubitOperator is unitary, i.e., it contains only one term with a
83+ coefficient, whose absolute value is 1, then one can apply it directly to
84+ qubits:
85+
86+ .. code-block:: python
87+
88+ eng = projectq.MainEngine()
89+ qureg = eng.allocate_qureg(6)
90+ QubitOperator('X0 X5', 1.j) | qureg # Applies X to qubit 0 and 5
91+ # with an additional global phase
92+ # of 1.j
93+
94+
7195 Attributes:
7296 terms (dict): **key**: A term represented by a tuple containing all
7397 non-trivial local Pauli operators ('X', 'Y', or 'Z').
@@ -128,6 +152,7 @@ def __init__(self, term=None, coefficient=1.):
128152 Raises:
129153 QubitOperatorError: Invalid operators provided to QubitOperator.
130154 """
155+ BasicGate .__init__ (self )
131156 if not isinstance (coefficient , (int , float , complex )):
132157 raise ValueError ('Coefficient must be a numeric type.' )
133158 self .terms = {}
@@ -226,6 +251,143 @@ def isclose(self, other, rel_tol=1e-12, abs_tol=1e-12):
226251 return False
227252 return True
228253
254+ def __or__ (self , qubits ):
255+ """
256+ Operator| overload which enables the following syntax:
257+
258+ .. code-block:: python
259+
260+ QubitOperator(...) | qureg
261+ QubitOperator(...) | (qureg,)
262+ QubitOperator(...) | qubit
263+ QubitOperator(...) | (qubit,)
264+
265+ Unlike other gates, this gate is only allowed to be applied to one
266+ quantum register or one qubit and only if the QubitOperator is
267+ unitary, i.e., consists of one term with a coefficient whose absolute
268+ values is 1.
269+
270+ Example:
271+
272+ .. code-block:: python
273+
274+ eng = projectq.MainEngine()
275+ qureg = eng.allocate_qureg(6)
276+ QubitOperator('X0 X5', 1.j) | qureg # Applies X to qubit 0 and 5
277+ # with an additional global
278+ # phase of 1.j
279+
280+ While in the above example the QubitOperator gate is applied to 6
281+ qubits, it only acts non-trivially on the two qubits qureg[0] and
282+ qureg[5]. Therefore, the operator| will create a new rescaled
283+ QubitOperator, i.e, it sends the equivalent of the following new gate
284+ to the MainEngine:
285+
286+ .. code-block:: python
287+
288+ QubitOperator('X0 X1', 1.j) | [qureg[0], qureg[5]]
289+
290+ which is only a two qubit gate.
291+
292+ Args:
293+ qubits: one Qubit object, one list of Qubit objects, one Qureg
294+ object, or a tuple of the former three cases.
295+
296+ Raises:
297+ TypeError: If QubitOperator is not unitary or applied to more than
298+ one quantum register.
299+ ValueError: If quantum register does not have enough qubits
300+ """
301+ # Check that input is only one qureg or one qubit
302+ qubits = self .make_tuple_of_qureg (qubits )
303+ if len (qubits ) != 1 :
304+ raise TypeError ("Only one qubit or qureg allowed." )
305+ # Check that operator is unitary
306+ if not len (self .terms ) == 1 :
307+ raise TypeError ("Too many terms. Only QubitOperators consisting "
308+ "of a single term (single n-qubit Pauli operator) "
309+ "with a coefficient of unit length can be applied "
310+ "to qubits with this function." )
311+ (term , coefficient ), = self .terms .items ()
312+ phase = cmath .phase (coefficient )
313+ if (abs (coefficient ) < 1 - EQ_TOLERANCE or
314+ abs (coefficient ) > 1 + EQ_TOLERANCE ):
315+ raise TypeError ("abs(coefficient) != 1. Only QubitOperators "
316+ "consisting of a single term (single n-qubit "
317+ "Pauli operator) with a coefficient of unit "
318+ "length can be applied to qubits with this "
319+ "function." )
320+ # Test if we need to apply only Ph
321+ if term == ():
322+ Ph (phase ) | qubits [0 ][0 ]
323+ return
324+ # Check that Qureg has enough qubits:
325+ num_qubits = len (qubits [0 ])
326+ non_trivial_qubits = set ()
327+ for index , action in term :
328+ non_trivial_qubits .add (index )
329+ if max (non_trivial_qubits ) >= num_qubits :
330+ raise ValueError ("QubitOperator acts on more qubits than the gate "
331+ "is applied to." )
332+ # Apply X, Y, Z, if QubitOperator acts only on one qubit
333+ if len (term ) == 1 :
334+ if term [0 ][1 ] == "X" :
335+ X | qubits [0 ][term [0 ][0 ]]
336+ elif term [0 ][1 ] == "Y" :
337+ Y | qubits [0 ][term [0 ][0 ]]
338+ elif term [0 ][1 ] == "Z" :
339+ Z | qubits [0 ][term [0 ][0 ]]
340+ Ph (phase ) | qubits [0 ][term [0 ][0 ]]
341+ return
342+ # Create new QubitOperator gate with rescaled qubit indices in
343+ # 0,..., len(non_trivial_qubits) - 1
344+ new_index = dict ()
345+ non_trivial_qubits = sorted (list (non_trivial_qubits ))
346+ for i in range (len (non_trivial_qubits )):
347+ new_index [non_trivial_qubits [i ]] = i
348+ new_qubitoperator = QubitOperator ()
349+ assert len (new_qubitoperator .terms ) == 0
350+ new_term = tuple ([(new_index [index ], action )
351+ for index , action in term ])
352+ new_qubitoperator .terms [new_term ] = coefficient
353+ new_qubits = [qubits [0 ][i ] for i in non_trivial_qubits ]
354+ # Apply new gate
355+ cmd = new_qubitoperator .generate_command (new_qubits )
356+ apply_command (cmd )
357+
358+ def get_inverse (self ):
359+ """
360+ Return the inverse gate of a QubitOperator if applied as a gate.
361+
362+ Raises:
363+ NotInvertible: Not implemented for QubitOperators which have
364+ multiple terms or a coefficient with absolute value
365+ not equal to 1.
366+ """
367+
368+ if len (self .terms ) == 1 :
369+ (term , coefficient ), = self .terms .items ()
370+ if (not abs (coefficient ) < 1 - EQ_TOLERANCE and not
371+ abs (coefficient ) > 1 + EQ_TOLERANCE ):
372+ return QubitOperator (term , coefficient ** (- 1 ))
373+ raise NotInvertible ("BasicGate: No get_inverse() implemented." )
374+
375+ def get_merged (self , other ):
376+ """
377+ Return this gate merged with another gate.
378+
379+ Standard implementation of get_merged:
380+
381+ Raises:
382+ NotMergeable: merging is not possible
383+ """
384+ if (isinstance (other , self .__class__ ) and
385+ len (other .terms ) == 1 and
386+ len (self .terms ) == 1 ):
387+ return self * other
388+ else :
389+ raise NotMergeable ()
390+
229391 def __imul__ (self , multiplier ):
230392 """
231393 In-place multiply (*=) terms with scalar or QubitOperator.
@@ -463,3 +625,6 @@ def __str__(self):
463625
464626 def __repr__ (self ):
465627 return str (self )
628+
629+ def __hash__ (self ):
630+ return hash (str (self ))
0 commit comments