def __init__(self, n_qubits=1): if n_qubits < 1: raise ValueError("A qReg must have length of at least 1.") init_state = [0 for i in range(2**n_qubits)] init_state[0] = 1 self.__q_reg = Qubit(init_state) self.__is_dereferenced = False
def test_qubit_product_two_args(self): ''' Verifies proper results for two args in ``Qubit.qubit_product()``. ''' expected_product = np.zeros(8, dtype=np.cdouble) expected_product[0] = -1j initial_state = Qubit([1j, 0]) actual_product = initial_state.qubit_product(Qubit([1j, 0]), Qubit([1j, 0])) np.testing.assert_array_equal(actual_product.state(), expected_product)
def test_computational_decomp_two_qubits(self): ''' Checks that ``Qubit.computational_decomp()`` is correct for a Bell pair. ''' bell_pair = Qubit([1, 0, 0, 1]) expected_decomposition = { '00': 1 / np.sqrt(2), '01': 0, '10': 0, '11': 1 / np.sqrt(2) } self.assertDictEqual(expected_decomposition, bell_pair.computational_decomp())
def test_string_rep_later_term_negative(self): ''' Checks that ``str(Qubit)`` is correct for a state with a term other than the first negative. ''' qubit = Qubit([1, 0, 0, 0, 0, 0, -2, 1]) expected_rep = '(4.08e-01)|000> + (-8.16e-01)|110> + (4.08e-01)|111>' self.assertEqual(expected_rep, str(qubit))
def test_string_rep_complex_term(self): ''' Checks that ``str(Qubit)`` is correct for a state with a complex term. ''' qubit = Qubit([1 - 1j, 1j]) expected_rep = '(5.77e-01-5.77e-01j)|0> + (5.77e-01j)|1>' self.assertEqual(expected_rep, str(qubit))
def test_string_rep_first_term_negative(self): ''' Checks that ``str(Qubit)`` is correct for a state with a negative first term. ''' qubit = Qubit([-1, 0, 0, 0, 0, 0, 2, 1]) expected_rep = '(-4.08e-01)|000> + (8.16e-01)|110> + (4.08e-01)|111>' self.assertEqual(expected_rep, str(qubit))
def test_string_rep_bell_state(self): ''' Checks that ``str(Qubit)`` is correct for a Bell state. ''' bell_pair = Qubit([1, 0, 0, -1]) expected_rep = '(7.07e-01)|00> + (-7.07e-01)|11>' self.assertEqual(expected_rep, str(bell_pair))
def __iadd__(self, n_new_qubits): ''' Prepends ``n_new_qubits`` qubits to the register in the |0> state. Leaves register unchanged if n_new_qubits is zero. ''' if self.__is_dereferenced: raise IllegalRegisterReference('Attempt to add Qubits to ' 'dereferenced register.') if not isinstance(n_new_qubits, int) or n_new_qubits < 0: raise ValueError("Can only add a nonnegative integer number of " "qubits to qReg.") n_qubits_in_zero_state = Qubit( [1 if i == 0 else 0 for i in range(2**n_new_qubits)]) self.__q_reg = n_qubits_in_zero_state.qubit_product(self.__q_reg) return self
def test_qubit_product_one_arg(self): ''' Verifies proper result for one arg in ``Qubit.qubit_product()``. ''' expected_product = np.array([0, 1, 0, 0]) q1 = Qubit([0, 1]) actual_product = self.test_qubit.qubit_product(q1) np.testing.assert_array_equal(actual_product.state(), expected_product)
def test_change_and_initialize_equiv(self): ''' Initalization of a ``Qubit`` as well as the ``Qubit.change_state()`` method should result in the same state if handed the same argument. ''' vectors = [[1, 0, 1, 1], [2, 14 - 8j], (0, 1, 0, 0, 0, 0, 0, 1)] for vector in vectors: self.test_qubit.change_state(vector) np.testing.assert_array_equal(self.test_qubit.state(), Qubit(vector).state())
class qReg: ''' A ``qReg`` is a high-level primitive which provides users with a representation of a quantum register. In this implementation, the quantum device on which the register exists is simulated via a ``pypsqueak.squeakcore.Qubit`` object. Like the underlying ``Qubit``, a ``qReg`` is initialized in the |0> state. Per the no-cloning theorem, any attempt to copy a ``qReg`` object will throw an exception. Additionally, operations which would otherwise leave duplicates of a specific instance of a ``qReg`` lying around 'dereference' the register. Once a ``qReg`` is dereferenced, any attempt to interact with the ``qReg`` will throw an exception. Since this implementation uses simulated quantum hardware, methods for examining the quantum state as a Dirac ket and returning the state as a numpy array are provided. They are ``qReg.peek()`` and ``qReg.dump_state()``, respectively. Examples -------- Here we demonstrate three ways to initialize a ``qReg`` with 3 qubits. >>> from pypsqueak.api import qReg, qOp >>> a = qReg(3) >>> b = qReg() >>> b += 2 >>> c = qReg() >>> identity_op = qOp() >>> identity_op.on(c, 2) >>> a == b False >>> a.dump_state() == b.dump_state() array([ True, True, True, True, True, True, True, True]) >>> a.dump_state() == c.dump_state() array([ True, True, True, True, True, True, True, True]) >>> a.peek() '(1.00e+00)|000>' Note that different instances of a ``qReg`` are considered unequal even if the underlying state is the same. Additionally, when ``qOp.on()`` is applied to a target in a ``qReg`` that is outside the range of the register, new filler qubits are automatically initialzed in the zero state. Now we demonstrate which operators are overloaded for ``qReg`` objects as well as their behavior. We can append any number of qubits to a ``qReg`` like so: >>> from pypsqueak.gates import X >>> a = qReg(1) >>> X.on(a, 1) >>> a += 3 >>> a.peek() '(1.00e+00)|00010>' Two registers can be joined into one via the tensor product. This can be done in place: >>> a *= qReg(2) >>> a.peek() '(1.00e+00)|0001000>' A new ``qReg`` can be created similarly: >>> a = qReg() >>> X.on(a, 0) >>> b = qReg() >>> c = a * b >>> c qReg(2) >>> c.peek() '(1.00e+00)|10>' >>> a Dereferenced qReg >>> a.peek() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "pypsqueak/api.py", line 199, in peek pypsqueak.errors.IllegalRegisterReference: Dereferenced register encountered. Notice that taking the product of ``qReg`` objects dereferences any operands. ''' def __init__(self, n_qubits=1): if n_qubits < 1: raise ValueError("A qReg must have length of at least 1.") init_state = [0 for i in range(2**n_qubits)] init_state[0] = 1 self.__q_reg = Qubit(init_state) self.__is_dereferenced = False def measure(self, target): ''' Performs a projective measurement on the qubit at the address ``target``. In this simulated implementation, there are three steps: #. Compute probability of each measurement using the amplitudes of each basis vector in the computational basis decomposition. #. Use these probabilities to randomly pick a measurement result. #. Project the ``qReg`` state onto the result's corresponding eigenspace. Parameters ---------- target : int The index of the qubit in the ``qReg`` to measure. An exception gets thrown if the value is negative or out of range. Returns ------- int Either a one or a zero, depending on the result of the measurement. The state of the ``qReg`` is projected onto the corresponding subspace. Examples -------- Here we prepare the Bell state and then measure qubit one in the ``qReg``. >>> from pypsqueak.api import qReg >>> from pypsqueak.gates import CNOT, H >>> a = qReg() >>> H.on(a, 0) >>> CNOT.on(a, 1, 0) >>> a.peek() '(7.07e-01)|00> + (7.07e-01)|11>' >>> a.measure(1) 1 >>> a.peek() '(1.00e+00)|11>' ''' self._throwExceptionIfRequestedMeasurementIsNotValid(target) measurement_outcome = self.measure_observable( self._makeComputationalBasisObservable(target)) # Two-state observable eigenvalues are 1 and -1, # so manual translation to 0 and 1 (respectively) is needed. return 0 if measurement_outcome == 1 else 1 def measure_observable(self, observable): ''' Performs a projective measurement of the ``observable`` corresponding to a ``qOp``. Parameters ---------- observable : pypsqueak.api.qOp The ``qOp`` corresponding to some observable to measure. An exception gets thrown if its size larger than the size of the ``qReg``. Returns ------- int One of the eigenvalues of ``observable``. The state of the ``qReg`` is projected onto the corresponding subspace. Examples -------- When the size of the operator is smaller than the ``qReg``, the the operator is prepended with identity operators. >>> from pypsqueak.api import qReg, qOp >>> from pypsqueak.gates import X >>> a = qReg(3) >>> X.on(a, 0) >>> a.peek() '(1.00e+00)|001>' >>> print(a.measure_observable(X)) -1.0 >>> a.peek() '(-7.07e-01)|000> + (7.07e-01)|001>' ''' self._throwExceptionIfObservableMeasurementIsNotValid(observable) observable = self._liftOperatorToDimensionOfRegister(observable) measurementEigenvalues, measurementTransitionMatrix = np.linalg.eig( observable._qOp__state.state()) measurementTransitionProbabilities = ( self._generateStateTransitionProbabilities( measurementTransitionMatrix)) measurementResult = np.random.choice( measurementEigenvalues, p=measurementTransitionProbabilities) self._collapseRegisterWavefunction(measurementResult, measurementEigenvalues, measurementTransitionMatrix) return measurementResult def peek(self): ''' Returns a ket description of the state of a ``qReg``. Would have no effect on hardware implementations of the backend. If the register has been dereferenced, raises an exception. Returns ------- str Description of ``qReg`` state. Has no side effects. Examples -------- Here we peek at a register in the Hadamard state: >>> from pypsqueak.api import qReg >>> from pypsqueak.gates import H >>> a = qReg(3) >>> H.on(a, 0) >>> a.peek() '(7.07e-01)|000> + (7.07e-01)|001>' After dereferencing the register via a multiplication, calling ``peek()`` raises an exception: >>> a * qReg() qReg(4) >>> a.peek() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "pypsqueak/api.py", line 309, in peek raise IllegalRegisterReference('Dereferenced register ' 'encountered.') pypsqueak.errors.IllegalRegisterReference: Dereferenced register encountered. ''' if self.__is_dereferenced: raise IllegalRegisterReference('Dereferenced register ' 'encountered.') return str(self.__q_reg) def dump_state(self): ''' Returns a copy of the state of a ``qReg`` as a numpy array. Would have no effect on a hardware implementation of the backend. If the register has been dereferenced, raises an exception. Returns ------- numpy.ndarray The state of ``qReg`` as a vector in the computational basis. Has no side effects. Examples -------- Here we get a vector corresponding to the Hadamard state: >>> from pypsqueak.api import qReg >>> from pypsqueak.gates import H >>> a = qReg(3) >>> H.on(a, 0) >>> a.dump_state() array([0.70710678, 0.70710678, 0. , 0. , 0. , 0. , 0. , 0. ]) Now we dereference the ``qReg`` and run into an exception when we try to dump its state again: >>> a * qReg() qReg(4) >>> a.dump_state() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "pypsqueak/api.py", line 342, in dump_state exception. pypsqueak.errors.IllegalRegisterReference: Dereferenced register encountered. ''' if self.__is_dereferenced: raise IllegalRegisterReference('Dereferenced register ' 'encountered.') return self.__q_reg.state() def _throwExceptionIfRequestedMeasurementIsNotValid(self, target): if self.__is_dereferenced: raise IllegalRegisterReference('Measurement attempted on ' 'dereferenced register.') isTargetQubitIndexValid = (isinstance(target, int) and target >= 0 and target < len(self)) if not isTargetQubitIndexValid: raise IndexError('Quantum register address must be nonnegative ' 'integer less than size of register.') def _throwExceptionIfObservableMeasurementIsNotValid(self, observable): if self.__is_dereferenced: raise IllegalRegisterReference("Measurement attempted on " "dereferenced register.") if not isinstance(observable, qOp): raise TypeError("Argument of measure_observable() must be a qOp.") if len(self) < observable.size(): raise WrongShapeError("Observable larger than qReg.") def _liftOperatorToDimensionOfRegister(self, operator): ''' Takes the ``qOp`` ``operator`` and if its size is smaller than the ``qReg``, returns a 'lifted' version of the operator that has been prepended with the tensor product of ``len(qReg) - operator.size()`` copies of the identity operator. If the size of ``operator`` already matches that of the ``qReg``, an unchanged version of ``operator`` is returned. Parameters ---------- operator : qOp The operator to be lifted. Returns ------- qOp A version of the operator lifted to be of the same dimension as the qReg. ''' if len(self) > operator.size(): diff = len(self) - operator.size() iden = qOp(np.eye(2**diff)) operator = iden.kron(operator) return operator def _makeComputationalBasisObservable(self, target): ''' Returns a ``qOp`` corresponding to the observable for a computational basis measurement on the qubit indexed by ``target``. Since this takes the form (identity operator)^m * (Pauli Z) * (identity operator)^m, with m + n + 1 equal the size of the ``qReg``, the eigenvalues of this operator are 1 and -1. ''' from pypsqueak.gates import I, Z if target == len(self) - 1: observable = Z else: observable = I for i in reversed(range(len(self) - 1)): if i == target: observable = observable.kron(Z) else: observable = observable.kron(I) return observable def _generateStateTransitionProbabilities(self, transitionMatrix): ''' Returns a list of the transition probabilities from the current ``qReg`` state to the set of normalized states specified by each column of the array ``transitionMatrix``. Parameters ---------- potentialTransitions : array A list of normalized eigenvectors representing possible new states for the ``qReg``. Returns ------- list A list of corresponding transition probabilities for each possible new state. ''' if not _is_unitary(transitionMatrix): raise NonUnitaryInputError("Non-unitary transition matrix " "encountered while computing " "qReg transition probabilities.") currentStateAsRowVector = self.__q_reg.state().T transitionAmplitudes = np.dot(currentStateAsRowVector, transitionMatrix) transitionProbabilities = [ amplitude * amplitude.conj() for amplitude in transitionAmplitudes ] return transitionProbabilities def _collapseRegisterWavefunction(self, measurementResult, measurementEigenvalues, measurementTransitionMatrix): ''' Collapses the ``qReg`` to the state corresponding to a measurement of ``measurementResult`` for an observable with eigenvalues given by the list ``measurementEigenvalues`` and normalized measurement outcomes given by the columns of ``measurementTransitionMatrix``. ''' collapsedState = np.dot( _makeProjectorOnToSubspace(measurementResult, measurementEigenvalues, measurementTransitionMatrix), self.__q_reg.state()) self.__q_reg.change_state(collapsedState) def __iadd__(self, n_new_qubits): ''' Prepends ``n_new_qubits`` qubits to the register in the |0> state. Leaves register unchanged if n_new_qubits is zero. ''' if self.__is_dereferenced: raise IllegalRegisterReference('Attempt to add Qubits to ' 'dereferenced register.') if not isinstance(n_new_qubits, int) or n_new_qubits < 0: raise ValueError("Can only add a nonnegative integer number of " "qubits to qReg.") n_qubits_in_zero_state = Qubit( [1 if i == 0 else 0 for i in range(2**n_new_qubits)]) self.__q_reg = n_qubits_in_zero_state.qubit_product(self.__q_reg) return self def __imul__(self, some_reg): ''' Concatentates the register with some_reg (|a_reg> *= |some_reg> stores |a_reg>|some_reg> into ``a_reg``). ''' if not isinstance(some_reg, qReg): raise TypeError("Cannot concatentate a non-qReg to a qReg.") if self.__is_dereferenced or some_reg.__is_dereferenced: raise IllegalRegisterReference( 'Concatentation attempted on dereferenced register.') self.__q_reg = self.__q_reg.qubit_product(some_reg._qReg__q_reg) some_reg._qReg__is_dereferenced = True return self def __mul__(self, another_reg): ''' For concatenating the register with another_reg (|new> = |reg> * |another_reg> stores the product into ``new``). ''' if self.__is_dereferenced or another_reg.__is_dereferenced: raise IllegalRegisterReference( 'Concatenation attempted on dereferenced register.') product_register = qReg() product_qubits = self.__q_reg.qubit_product(another_reg._qReg__q_reg) product_register._qReg__q_reg.change_state(product_qubits.state()) self.__is_dereferenced = True another_reg._qReg__is_dereferenced = True return product_register def __len__(self): if self.__is_dereferenced: raise IllegalRegisterReference( 'Dereferenced register encountered.') return len(self.__q_reg) def __copy__(self): raise IllegalCopyAttempt('Cannot copy a qReg.') def __deepcopy__(self, memo): raise IllegalCopyAttempt('Cannot copy a qReg.') def __repr__(self): if not self.__is_dereferenced: return "qReg({})".format(len(self)) else: return "Dereferenced qReg"
def test_empty_input_qubit_product(self): ''' ``Qubit.qubit_product()`` with empty argument should raise a ``TypeError``. ''' self.assertRaises(TypeError, Qubit().qubit_product)
def setUp(self): self.test_qubit = Qubit()
class QubitValidInput(unittest.TestCase): valid_qubits = [[1.0, 0], [0.0, 1], [1.0j, 0], [1.0j / np.sqrt(2), -1j / np.sqrt(2)], [24.0, 6], (24.0, 6), [1 - 1j, 0j], [-25.0, 9j], [-25.0, 9j, 3, 5], np.array([1.0, 0])] def setUp(self): self.test_qubit = Qubit() def test_change_and_initialize_equiv(self): ''' Initalization of a ``Qubit`` as well as the ``Qubit.change_state()`` method should result in the same state if handed the same argument. ''' vectors = [[1, 0, 1, 1], [2, 14 - 8j], (0, 1, 0, 0, 0, 0, 0, 1)] for vector in vectors: self.test_qubit.change_state(vector) np.testing.assert_array_equal(self.test_qubit.state(), Qubit(vector).state()) def test_normalize_valid_input(self): ''' ``Qubit.change_state()`` should rescale valid new states to unit vectors. ''' for vec in self.valid_qubits: # get machine epsilon for relative error mach_eps = np.finfo(type(vec[0])).eps # change state and compute inner product self.test_qubit.change_state(vec) state_dual = np.conjugate(self.test_qubit.state()) # if inner product != 1, then test_qubit.normalize() failed inner_product = np.dot(self.test_qubit.state(), state_dual) # cmath is used so we don't discard errors in the imaginary part norm_query = cmath.isclose(1, inner_product, rel_tol=10 * mach_eps) self.assertTrue(norm_query) def test_qubit_product_one_arg(self): ''' Verifies proper result for one arg in ``Qubit.qubit_product()``. ''' expected_product = np.array([0, 1, 0, 0]) q1 = Qubit([0, 1]) actual_product = self.test_qubit.qubit_product(q1) np.testing.assert_array_equal(actual_product.state(), expected_product) def test_qubit_product_two_args(self): ''' Verifies proper results for two args in ``Qubit.qubit_product()``. ''' expected_product = np.zeros(8, dtype=np.cdouble) expected_product[0] = -1j initial_state = Qubit([1j, 0]) actual_product = initial_state.qubit_product(Qubit([1j, 0]), Qubit([1j, 0])) np.testing.assert_array_equal(actual_product.state(), expected_product) def test_computational_decomp_two_qubits(self): ''' Checks that ``Qubit.computational_decomp()`` is correct for a Bell pair. ''' bell_pair = Qubit([1, 0, 0, 1]) expected_decomposition = { '00': 1 / np.sqrt(2), '01': 0, '10': 0, '11': 1 / np.sqrt(2) } self.assertDictEqual(expected_decomposition, bell_pair.computational_decomp()) def test_computational_decomp_three_qubits(self): ''' Checks that ``Qubit.computational_decomp()`` is correct for a three qubit state. ''' bell_pair = Qubit([1, 0, 1, 0, 0, 0, 0, 1]) expected_decomposition = { '000': 1 / np.sqrt(3), '001': 0, '010': 1 / np.sqrt(3), '011': 0, '100': 0, '101': 0, '110': 0, '111': 1 / np.sqrt(3) } self.assertDictEqual(expected_decomposition, bell_pair.computational_decomp()) def test_string_rep_bell_state(self): ''' Checks that ``str(Qubit)`` is correct for a Bell state. ''' bell_pair = Qubit([1, 0, 0, -1]) expected_rep = '(7.07e-01)|00> + (-7.07e-01)|11>' self.assertEqual(expected_rep, str(bell_pair)) def test_string_rep_first_term_negative(self): ''' Checks that ``str(Qubit)`` is correct for a state with a negative first term. ''' qubit = Qubit([-1, 0, 0, 0, 0, 0, 2, 1]) expected_rep = '(-4.08e-01)|000> + (8.16e-01)|110> + (4.08e-01)|111>' self.assertEqual(expected_rep, str(qubit)) def test_string_rep_later_term_negative(self): ''' Checks that ``str(Qubit)`` is correct for a state with a term other than the first negative. ''' qubit = Qubit([1, 0, 0, 0, 0, 0, -2, 1]) expected_rep = '(4.08e-01)|000> + (-8.16e-01)|110> + (4.08e-01)|111>' self.assertEqual(expected_rep, str(qubit)) def test_string_rep_complex_term(self): ''' Checks that ``str(Qubit)`` is correct for a state with a complex term. ''' qubit = Qubit([1 - 1j, 1j]) expected_rep = '(5.77e-01-5.77e-01j)|0> + (5.77e-01j)|1>' self.assertEqual(expected_rep, str(qubit))