class TestDefaultQubitDevice(BaseTest): """Test the default qubit device. The test ensures that the device is properly applying qubit operations and calculating the correct observables.""" def setUp(self): self.dev = DefaultQubit(wires=2, shots=0) def test_operation_map(self): """Test that default qubit device supports all PennyLane discrete gates.""" self.logTestName() self.assertEqual(set(qml.ops._qubit__ops__), set(self.dev._operation_map)) def test_observable_map(self): """Test that default qubit device supports all PennyLane discrete observables.""" self.logTestName() self.assertEqual( set(qml.ops._qubit__obs__) | {"Identity"}, set(self.dev._observable_map)) def test_get_operator_matrix(self): """Test the the correct matrix is returned given an operation name""" self.logTestName() for name, fn in { **self.dev._operation_map, **self.dev._observable_map, }.items(): try: op = getattr(qml.ops, name) except AttributeError: op = getattr(qml.expval, name) p = [0.432423, -0.12312, 0.324][:op.num_params] if name == "QubitStateVector": p = [np.array([1, 0, 0, 0])] elif name == "QubitUnitary": p = [U] elif name == "Hermitian": p = [H] res = self.dev._get_operator_matrix(name, p) if callable(fn): # if the default.qubit is an operation accepting parameters, # initialise it using the parameters generated above. expected = fn(*p) else: # otherwise, the operation is simply an array. expected = fn self.assertAllAlmostEqual(res, expected, delta=self.tol) def test_apply(self): """Test the application of gates to a state""" self.logTestName() self.dev.reset() # loop through all supported operations for gate_name, fn in self.dev._operation_map.items(): log.debug("\tTesting %s gate...", gate_name) # start in the state |00> self.dev._state = np.array([1, 0, 0, 0]) # get the equivalent pennylane operation class op = getattr(qml.ops, gate_name) # the list of wires to apply the operation to w = list(range(op.num_wires)) if op.par_domain == "A": # the parameter is an array if gate_name == "QubitStateVector": p = [np.array([1, 0, 1, 1]) / np.sqrt(3)] expected_out = p elif gate_name == "QubitUnitary": p = [U] w = [0] expected_out = np.kron(U @ np.array([1, 0]), np.array([1, 0])) elif gate_name == "BasisState": p = [np.array([1, 1])] expected_out = np.array([0, 0, 0, 1]) elif op.par_domain == "N": # the parameter is an integer p = [1, 3, 4][:op.num_params] else: # the parameter is a float p = [0.432423, -0.12312, 0.324][:op.num_params] if callable(fn): # if the default.qubit is an operation accepting parameters, # initialise it using the parameters generated above. O = fn(*p) else: # otherwise, the operation is simply an array. O = fn # calculate the expected output if op.num_wires == 1: expected_out = np.kron(O @ np.array([1, 0]), np.array([1, 0])) elif op.num_wires == 2: expected_out = O @ self.dev._state self.dev.apply(gate_name, wires=w, par=p) # verify the device is now in the expected state self.assertAllAlmostEqual(self.dev._state, expected_out, delta=self.tol) def test_apply_errors(self): """Test that apply fails for incorrect state preparation, and > 2 qubit gates""" self.logTestName() with self.assertRaisesRegex( ValueError, r"State vector must be of length 2\*\*wires."): p = [np.array([1, 0, 1, 1, 1]) / np.sqrt(3)] self.dev.apply("QubitStateVector", wires=[0, 1], par=[p]) with self.assertRaisesRegex( ValueError, "BasisState parameter must be an array of 0 or 1 integers of length at most 2.", ): self.dev.apply("BasisState", wires=[0, 1], par=[np.array([-0.2, 4.2])]) with self.assertRaisesRegex( ValueError, "The default.qubit plugin can apply BasisState only to all of the 2 wires.", ): self.dev.apply("BasisState", wires=[0, 1, 2], par=[np.array([0, 1])]) def test_ev(self): """Test that expectation values are calculated correctly""" self.logTestName() self.dev.reset() # loop through all supported observables for name, fn in self.dev._observable_map.items(): log.debug("\tTesting %s observable...", name) # start in the state |00> self.dev._state = np.array([1, 0, 1, 1]) / np.sqrt(3) # get the equivalent pennylane operation class op = getattr(qml.ops, name) if op.par_domain == "A": # the parameter is an array p = [H] else: # the parameter is a float p = [0.432423, -0.12312, 0.324][:op.num_params] if callable(fn): # if the default.qubit is an operation accepting parameters, # initialise it using the parameters generated above. O = fn(*p) else: # otherwise, the operation is simply an array. O = fn # calculate the expected output if op.num_wires == 1 or op.num_wires == 0: expected_out = self.dev._state.conj() @ np.kron( O, I) @ self.dev._state elif op.num_wires == 2: expected_out = self.dev._state.conj() @ O @ self.dev._state else: raise NotImplementedError( "Test for operations with num_wires=" + op.num_wires + " not implemented.") res = self.dev.ev(O, wires=[0]) # verify the device is now in the expected state self.assertAllAlmostEqual(res, expected_out, delta=self.tol) # text warning raised if matrix is complex with pytest.warns(RuntimeWarning, match='Nonvanishing imaginary part'): self.dev.ev(H + 1j, [0]) def test_var_pauliz(self): """Test that variance of PauliZ is the same as I-<Z>^2""" self.logTestName() self.dev.reset() phi = 0.543 theta = 0.6543 self.dev.apply('RX', wires=[0], par=[phi]) self.dev.apply('RY', wires=[0], par=[theta]) var = self.dev.var('PauliZ', [0], []) mean = self.dev.expval('PauliZ', [0], []) self.assertAlmostEqual(var, 1 - mean**2, delta=self.tol) def test_var_pauliz_rotated_state(self): """test correct variance for <Z> of a rotated state""" self.logTestName() self.dev.reset() phi = 0.543 theta = 0.6543 self.dev.apply('RX', wires=[0], par=[phi]) self.dev.apply('RY', wires=[0], par=[theta]) var = self.dev.var('PauliZ', [0], []) expected = 0.25 * (3 - np.cos(2 * theta) - 2 * np.cos(theta)**2 * np.cos(2 * phi)) self.assertAlmostEqual(var, expected, delta=self.tol) def test_var_hermitian_rotated_state(self): """test correct variance for <H> of a rotated state""" self.logTestName() self.dev.reset() phi = 0.543 theta = 0.6543 H = np.array([[4, -1 + 6j], [-1 - 6j, 2]]) self.dev.apply('RX', wires=[0], par=[phi]) self.dev.apply('RY', wires=[0], par=[theta]) var = self.dev.var('Hermitian', [0], [H]) expected = 0.5*(2*np.sin(2*theta)*np.cos(phi)**2+24*np.sin(phi)\ *np.cos(phi)*(np.sin(theta)-np.cos(theta))+35*np.cos(2*phi)+39) self.assertAlmostEqual(var, expected, delta=self.tol)
class TestDefaultQubitDevice(BaseTest): """Test the default qubit device. The test ensures that the device is properly applying qubit operations and calculating the correct observables.""" def setUp(self): self.dev = DefaultQubit(wires=2, shots=0) def test_operation_map(self): """Test that default qubit device supports all PennyLane discrete gates.""" self.logTestName() self.assertEqual(set(qml.ops.qubit.__all__), set(self.dev._operation_map)) def test_expectation_map(self): """Test that default qubit device supports all PennyLane discrete expectations.""" self.logTestName() self.assertEqual( set(qml.expval.qubit.__all__) | {"Identity"}, set(self.dev._expectation_map)) def test_expand_one(self): """Test that a 1 qubit gate correctly expands to 3 qubits.""" self.logTestName() dev = DefaultQubit(wires=3) # test applied to wire 0 res = dev.expand_one(U, [0]) expected = np.kron(np.kron(U, I), I) self.assertAllAlmostEqual(res, expected, delta=self.tol) # test applied to wire 1 res = dev.expand_one(U, [1]) expected = np.kron(np.kron(I, U), I) self.assertAllAlmostEqual(res, expected, delta=self.tol) # test applied to wire 2 res = dev.expand_one(U, [2]) expected = np.kron(np.kron(I, I), U) self.assertAllAlmostEqual(res, expected, delta=self.tol) # test exception raised if U is not 2x2 matrix with self.assertRaisesRegex(ValueError, "2x2 matrix required"): dev.expand_one(U2, [0]) # test exception raised if more than one subsystem provided with self.assertRaisesRegex(ValueError, "One target subsystem required"): dev.expand_one(U, [0, 1]) def test_expand_two(self): """Test that a 2 qubit gate correctly expands to 3 qubits.""" self.logTestName() dev = DefaultQubit(wires=4) # test applied to wire 0+1 res = dev.expand_two(U2, [0, 1]) expected = np.kron(np.kron(U2, I), I) self.assertAllAlmostEqual(res, expected, delta=self.tol) # test applied to wire 1+2 res = dev.expand_two(U2, [1, 2]) expected = np.kron(np.kron(I, U2), I) self.assertAllAlmostEqual(res, expected, delta=self.tol) # test applied to wire 2+3 res = dev.expand_two(U2, [2, 3]) expected = np.kron(np.kron(I, I), U2) self.assertAllAlmostEqual(res, expected, delta=self.tol) # CNOT with target on wire 1 res = dev.expand_two(CNOT, [1, 0]) rows = np.array([0, 2, 1, 3]) expected = np.kron(np.kron(CNOT[:, rows][rows], I), I) self.assertAllAlmostEqual(res, expected, delta=self.tol) # test exception raised if U is not 4x4 matrix with self.assertRaisesRegex(ValueError, "4x4 matrix required"): dev.expand_two(U, [0]) # test exception raised if two subsystems are not provided with self.assertRaisesRegex(ValueError, "Two target subsystems required"): dev.expand_two(U2, [0]) # test exception raised if unphysical subsystems provided with self.assertRaisesRegex(ValueError, "Bad target subsystems."): dev.expand_two(U2, [-1, 5]) def test_expand_multi(self): """Test that any arbitrary qubit gate correctly expands to the full system.""" pass def test_get_operator_matrix(self): """Test the the correct matrix is returned given an operation name""" self.logTestName() for name, fn in { **self.dev._operation_map, **self.dev._expectation_map, }.items(): try: op = qml.ops.__getattribute__(name) except AttributeError: op = qml.expval.__getattribute__(name) p = [0.432423, -0.12312, 0.324][:op.num_params] if name == "QubitStateVector": p = [np.array([1, 0, 0, 0])] elif name == "QubitUnitary": p = [U] elif name == "Hermitian": p = [H] res = self.dev._get_operator_matrix(name, p) if callable(fn): # if the default.qubit is an operation accepting parameters, # initialise it using the parameters generated above. expected = fn(*p) else: # otherwise, the operation is simply an array. expected = fn self.assertAllAlmostEqual(res, expected, delta=self.tol) def test_apply(self): """Test the application of gates to a state""" self.logTestName() self.dev.reset() # loop through all supported operations for gate_name, fn in self.dev._operation_map.items(): log.debug("\tTesting %s gate...", gate_name) # start in the state |00> self.dev._state = np.array([1, 0, 0, 0]) # get the equivalent pennylane operation class op = qml.ops.__getattribute__(gate_name) # the list of wires to apply the operation to w = list(range(op.num_wires)) if op.par_domain == "A": # the parameter is an array if gate_name == "QubitStateVector": p = [np.array([1, 0, 1, 1]) / np.sqrt(3)] expected_out = p elif gate_name == "QubitUnitary": p = [U] w = [0] expected_out = np.kron(U @ np.array([1, 0]), np.array([1, 0])) elif gate_name == "BasisState": p = [np.array([1, 1])] expected_out = np.array([0, 0, 0, 1]) elif op.par_domain == "N": # the parameter is an integer p = [1, 3, 4][:op.num_params] else: # the parameter is a float p = [0.432423, -0.12312, 0.324][:op.num_params] if callable(fn): # if the default.qubit is an operation accepting parameters, # initialise it using the parameters generated above. O = fn(*p) else: # otherwise, the operation is simply an array. O = fn # calculate the expected output if op.num_wires == 1: expected_out = np.kron(O @ np.array([1, 0]), np.array([1, 0])) elif op.num_wires == 2: expected_out = O @ self.dev._state self.dev.apply(gate_name, wires=w, par=p) # verify the device is now in the expected state self.assertAllAlmostEqual(self.dev._state, expected_out, delta=self.tol) def test_apply_errors(self): """Test that apply fails for incorrect state preparation, and > 2 qubit gates""" self.logTestName() with self.assertRaisesRegex( ValueError, r"State vector must be of length 2\*\*wires."): p = [np.array([1, 0, 1, 1, 1]) / np.sqrt(3)] self.dev.apply("QubitStateVector", wires=[0, 1], par=[p]) with self.assertRaisesRegex( ValueError, "BasisState parameter must be an array of 0 or 1 integers of length at most 2.", ): self.dev.apply("BasisState", wires=[0, 1], par=[np.array([-0.2, 4.2])]) with self.assertRaisesRegex( ValueError, "The default.qubit plugin can apply BasisState only to all of the 2 wires.", ): self.dev.apply("BasisState", wires=[0, 1, 2], par=[np.array([0, 1])]) with self.assertRaisesRegex( ValueError, "This plugin supports only one- and two-qubit gates."): self.dev.apply("QubitUnitary", wires=[0, 1, 2], par=[U2]) def test_ev(self): """Test that expectation values are calculated correctly""" self.logTestName() self.dev.reset() # loop through all supported observables for name, fn in self.dev._expectation_map.items(): print(name) log.debug("\tTesting %s observable...", name) # start in the state |00> self.dev._state = np.array([1, 0, 1, 1]) / np.sqrt(3) # get the equivalent pennylane operation class op = qml.expval.__getattribute__(name) if op.par_domain == "A": # the parameter is an array p = [H] else: # the parameter is a float p = [0.432423, -0.12312, 0.324][:op.num_params] if callable(fn): # if the default.qubit is an operation accepting parameters, # initialise it using the parameters generated above. O = fn(*p) else: # otherwise, the operation is simply an array. O = fn print("op.num_wires=" + str(op.num_wires)) # calculate the expected output if op.num_wires == 1 or op.num_wires == 0: expected_out = self.dev._state.conj() @ np.kron( O, I) @ self.dev._state elif op.num_wires == 2: expected_out = self.dev._state.conj() @ O @ self.dev._state else: raise NotImplementedError( "Test for operations with num_wires=" + op.num_wires + " not implemented.") res = self.dev.ev(O, wires=[0]) # verify the device is now in the expected state self.assertAllAlmostEqual(res, expected_out, delta=self.tol) # text exception raised if matrix is not 2x2 or 4x4 with self.assertRaisesRegex( ValueError, "Only one and two-qubit expectation is supported."): self.dev.ev(U_toffoli, [0]) # text warning raised if matrix is complex with self.assertLogs(level="WARNING") as l: self.dev.ev(H + 1j, [0]) self.assertEqual(len(l.output), 1) self.assertEqual(len(l.records), 1) self.assertIn("Nonvanishing imaginary part", l.output[0])