def test_incorrect_heisenberg_size(self, monkeypatch): """The number of dimensions of a CV observable Heisenberg representation does not match the ev_order attribute.""" monkeypatch.setattr(qml.P, "ev_order", 2) with pytest.raises(ValueError, match="Mismatch between the polynomial order"): _transform_observable(qml.P(0), np.identity(3), device_wires=[0])
def test_vacuum_state(self): """Test the vacuum state is correct.""" self.logTestName() wires = 3 means, cov = vacuum_state(wires, hbar=hbar) self.assertAllAlmostEqual(means, np.zeros([2*wires]), delta=self.tol) self.assertAllAlmostEqual(cov, np.identity(2*wires)*hbar/2, delta=self.tol)
def test_controlled_arbitrary_rotation(self): """Test controlled arbitrary rotation is correct""" self.logTestName() # test identity for phi,theta,omega=0 self.assertAllAlmostEqual(CRot3(0,0,0), np.identity(4), delta=self.tol) # test identity for phi,theta,omega=pi expected = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1], [0, 0, 1, 0]]) self.assertAllAlmostEqual(CRot3(np.pi,np.pi,np.pi), expected, delta=self.tol) def arbitrary_Crotation(x, y, z): """controlled arbitrary single qubit rotation""" c = np.cos(y / 2) s = np.sin(y / 2) return np.array( [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, np.exp(-0.5j * (x + z)) * c, -np.exp(0.5j * (x - z)) * s], [0, 0, np.exp(-0.5j * (x - z)) * s, np.exp(0.5j * (x + z)) * c] ] ) a, b, c = 0.432, -0.152, 0.9234 self.assertAllAlmostEqual( CRot3(a, b, c), arbitrary_Crotation(a, b, c), delta=self.tol )
def test_coherent_state(self): """Test the coherent state is correct.""" self.logTestName() a = 0.432-0.123j means, cov = coherent_state(a, hbar=hbar) self.assertAllAlmostEqual(means, np.array([a.real, a.imag])*np.sqrt(2*hbar), delta=self.tol) self.assertAllAlmostEqual(cov, np.identity(2)*hbar/2, delta=self.tol)
def test_higher_order_observable(self, monkeypatch): """An exception should be raised if the observable is higher than 2nd order.""" monkeypatch.setattr(qml.P, "ev_order", 3) with pytest.raises(NotImplementedError, match="order > 2 not implemented"): _transform_observable(qml.P(0), np.identity(3), device_wires=[0])
def test_controlled_arbitrary_rotation(self, tol): """Test controlled arbitrary rotation is correct""" # test identity for phi,theta,omega=0 assert np.allclose(CRot3(0, 0, 0), np.identity(4), atol=tol, rtol=0) # test identity for phi,theta,omega=pi expected = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1], [0, 0, 1, 0]]) assert np.allclose(CRot3(np.pi, np.pi, np.pi), expected, atol=tol, rtol=0) def arbitrary_Crotation(x, y, z): """controlled arbitrary single qubit rotation""" c = np.cos(y / 2) s = np.sin(y / 2) return np.array( [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, np.exp(-0.5j * (x + z)) * c, -np.exp(0.5j * (x - z)) * s], [0, 0, np.exp(-0.5j * (x - z)) * s, np.exp(0.5j * (x + z)) * c] ] ) a, b, c = 0.432, -0.152, 0.9234 assert np.allclose(CRot3(a, b, c), arbitrary_Crotation(a, b, c), atol=tol, rtol=0)
def step_and_cost(self, qnode, *args, grad_fn=None, recompute_tensor=True, metric_tensor_fn=None, **kwargs): """Update the parameter array :math:`x` with one step of the optimizer and return the corresponding objective function value prior to the step. Args: qnode (QNode): the QNode for optimization *args : variable length argument list for qnode grad_fn (function): optional gradient function of the qnode with respect to the variables ``*args``. If ``None``, the gradient function is computed automatically. Must return a ``tuple[array]`` with the same number of elements as ``*args``. Each array of the tuple should have the same shape as the corresponding argument. recompute_tensor (bool): Whether or not the metric tensor should be recomputed. If not, the metric tensor from the previous optimization step is used. metric_tensor_fn (function): Optional metric tensor function with respect to the variables ``args``. If ``None``, the metric tensor function is computed automatically. **kwargs : variable length of keyword arguments for the qnode Returns: tuple: the new variable values :math:`x^{(t+1)}` and the objective function output prior to the step """ # pylint: disable=arguments-differ if not isinstance( qnode, (qml.QNode, qml.ExpvalCost)) and metric_tensor_fn is None: raise ValueError( "The objective function must either be encoded as a single QNode or " "an ExpvalCost object for the natural gradient to be automatically computed. " "Otherwise, metric_tensor_fn must be explicitly provided to the optimizer." ) if recompute_tensor or self.metric_tensor is None: if metric_tensor_fn is None: metric_tensor_fn = qml.metric_tensor(qnode, approx=self.approx) self.metric_tensor = metric_tensor_fn(*args, **kwargs) self.metric_tensor += self.lam * np.identity( self.metric_tensor.shape[0]) g, forward = self.compute_grad(qnode, args, kwargs, grad_fn=grad_fn) new_args = np.array(self.apply_grad(g, args), requires_grad=True) if forward is None: forward = qnode(*args, **kwargs) # unwrap from list if one argument, cleaner return if len(new_args) == 1: return new_args[0], forward return new_args, forward
def test_phase_shift(self, tol): """Test phase shift is correct""" # test identity for theta=0 assert np.allclose(Rphi(0), np.identity(2), atol=tol, rtol=0) # test arbitrary phase shift phi = 0.5432 expected = np.array([[1, 0], [0, np.exp(1j * phi)]]) assert np.allclose(Rphi(phi), expected, atol=tol, rtol=0)
def test_phase_shift(self): """Test phase shift is correct""" self.logTestName() # test identity for theta=0 self.assertAllAlmostEqual(Rphi(0), np.identity(2), delta=self.tol) # test arbitrary phase shift phi = 0.5432 expected = np.array([[1, 0], [0, np.exp(1j * phi)]]) self.assertAllAlmostEqual(Rphi(phi), expected, delta=self.tol)
def test_z_rotation(self, tol): """Test z rotation is correct""" # test identity for theta=0 assert np.allclose(Rotz(0), np.identity(2), atol=tol, rtol=0) # test identity for theta=pi/2 expected = np.diag(np.exp([-1j * np.pi / 4, 1j * np.pi / 4])) assert np.allclose(Rotz(np.pi / 2), expected, atol=tol, rtol=0) # test identity for theta=pi assert np.allclose(Rotz(np.pi), -1j * Z, atol=tol, rtol=0)
def test_y_rotation(self, tol): """Test y rotation is correct""" # test identity for theta=0 assert np.allclose(Roty(0), np.identity(2), atol=tol, rtol=0) # test identity for theta=pi/2 expected = np.array([[1, -1], [1, 1]]) / np.sqrt(2) assert np.allclose(Roty(np.pi / 2), expected, atol=tol, rtol=0) # test identity for theta=pi expected = np.array([[0, -1], [1, 0]]) assert np.allclose(Roty(np.pi), expected, atol=tol, rtol=0)
def test_z_rotation(self): """Test z rotation is correct""" self.logTestName() # test identity for theta=0 self.assertAllAlmostEqual(Rotz(0), np.identity(2), delta=self.tol) # test identity for theta=pi/2 expected = np.diag(np.exp([-1j * np.pi / 4, 1j * np.pi / 4])) self.assertAllAlmostEqual(Rotz(np.pi / 2), expected, delta=self.tol) # test identity for theta=pi self.assertAllAlmostEqual(Rotz(np.pi), -1j * Z, delta=self.tol)
def test_C_z_rotation(self, tol): """Test controlled z rotation is correct""" # test identity for theta=0 assert np.allclose(CRotz(0), np.identity(4), atol=tol, rtol=0) # test identity for theta=pi/2 expected = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, np.exp(-1j * np.pi / 4), 0], [0, 0, 0, np.exp(1j * np.pi / 4)]]) assert np.allclose(CRotz(np.pi / 2), expected, atol=tol, rtol=0) # test identity for theta=pi expected = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1j, 0], [0, 0, 0, 1j]]) assert np.allclose(CRotz(np.pi), expected, atol=tol, rtol=0)
def test_C_y_rotation(self, tol): """Test controlled y rotation is correct""" # test identity for theta=0 assert np.allclose(CRoty(0), np.identity(4), atol=tol, rtol=0) # test identity for theta=pi/2 expected = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1/np.sqrt(2), -1/np.sqrt(2)], [0, 0, 1/np.sqrt(2), 1/np.sqrt(2)]]) assert np.allclose(CRoty(np.pi / 2), expected, atol=tol, rtol=0) # test identity for theta=pi expected = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1], [0, 0, 1, 0]]) assert np.allclose(CRoty(np.pi), expected, atol=tol, rtol=0)
def test_C_y_rotation(self): """Test controlled y rotation is correct""" self.logTestName() # test identity for theta=0 self.assertAllAlmostEqual(CRoty(0), np.identity(4), delta=self.tol) # test identity for theta=pi/2 expected = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1/np.sqrt(2), -1/np.sqrt(2)], [0, 0, 1/np.sqrt(2), 1/np.sqrt(2)]]) self.assertAllAlmostEqual(CRoty(np.pi / 2), expected, delta=self.tol) # test identity for theta=pi expected = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1], [0, 0, 1, 0]]) self.assertAllAlmostEqual(CRoty(np.pi), expected, delta=self.tol)
def test_y_rotation(self): """Test y rotation is correct""" self.logTestName() # test identity for theta=0 self.assertAllAlmostEqual(Roty(0), np.identity(2), delta=self.tol) # test identity for theta=pi/2 expected = np.array([[1, -1], [1, 1]]) / np.sqrt(2) self.assertAllAlmostEqual(Roty(np.pi / 2), expected, delta=self.tol) # test identity for theta=pi expected = np.array([[0, -1], [1, 0]]) self.assertAllAlmostEqual(Roty(np.pi), expected, delta=self.tol)
def test_C_z_rotation(self): """Test controlled z rotation is correct""" self.logTestName() # test identity for theta=0 self.assertAllAlmostEqual(CRotz(0), np.identity(4), delta=self.tol) # test identity for theta=pi/2 expected = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, np.exp(-1j * np.pi / 4), 0], [0, 0, 0, np.exp(1j * np.pi / 4)]]) self.assertAllAlmostEqual(CRotz(np.pi / 2), expected, delta=self.tol) # test identity for theta=pi expected = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1j, 0], [0, 0, 0, 1j]]) self.assertAllAlmostEqual(CRotz(np.pi), expected, delta=self.tol)
def reference(*x): """reference circuit""" if callable(qop): # if the default.qubit is an operation accepting parameters, # initialise it using the parameters generated above. O = qop(*x) else: # otherwise, the operation is simply an array. O = qop # calculate the expected output out_state = np.kron(Rotx(a) @ np.array([1, 0]), np.array([1, 0])) expectation = out_state.conj() @ np.kron(O, np.identity(2)) @ out_state return expectation
def GenHamiltonian(self): ''' Define and diagonalize chains hamiltonian ''' # Define Pauli operators PauliX = np.array([ [0, 1], [1, 0] ]) PauliY = np.array([ [0, -1j], [1j, 0] ]) PauliZ = np.array([ [1, 0], [0, -1] ]) PauliOps = [PauliX, PauliY, PauliZ] # Definition of two-qubit Hamiltonian Hij = np.sum(Jint * np.kron(Pauli, Pauli) for Jint, Pauli in zip(self.ExchangeIntegrals, PauliOps)) # Definition of one-qubit Hamiltonian Hi = np.sum(hcomp * Pauli for hcomp, Pauli in zip(self.ExternalField, PauliOps)) # Definition of Chain Hamiltonian Hchain = np.sum( np.kron(np.identity(2**idx), np.kron(Hij, np.identity(2**(self.num_spins-(idx + 2))))) + np.kron(np.identity(2**idx), np.kron(Hi, np.identity(2**(self.num_spins-(idx + 1))))) for idx in range(self.num_spins-1) ) + np.kron(np.identity(2**(self.num_spins - 1)), Hi) # Diagonalization of Hamiltonian self.HamMatEnergies, self.HamMatEstates = np.linalg.eig(Hchain) # Storing system hamiltonian self.SysHamiltonian = qml.Hermitian( Hchain, wires=range(self.num_spins))
def test_arbitrary_rotation(self, tol): """Test arbitrary single qubit rotation is correct""" # test identity for phi,theta,omega=0 assert np.allclose(Rot3(0, 0, 0), np.identity(2), atol=tol, rtol=0) # expected result def arbitrary_rotation(x, y, z): """arbitrary single qubit rotation""" c = np.cos(y / 2) s = np.sin(y / 2) return np.array( [ [np.exp(-0.5j * (x + z)) * c, -np.exp(0.5j * (x - z)) * s], [np.exp(-0.5j * (x - z)) * s, np.exp(0.5j * (x + z)) * c], ] ) a, b, c = 0.432, -0.152, 0.9234 assert np.allclose(Rot3(a, b, c), arbitrary_rotation(a, b, c), atol=tol, rtol=0)
def reference(*x): """reference circuit""" if g == 'GaussianState': return x[0][0] if g == 'Displacement': alpha = x[0]*np.exp(1j*x[1]) return (alpha+a).real*np.sqrt(2*hbar) if 'State' in g: mu, _ = qop(*x, hbar=hbar) return mu[0] S = qop(*x) # calculate the expected output if op.num_wires == 1: S = block_diag(S, np.identity(2))[:, [0, 2, 1, 3]][[0, 2, 1, 3]] return (S @ np.array([a.real, a.imag, 0, 0])*np.sqrt(2*hbar))[0]
def test_controlled_phase(self): """Test the CZ symplectic transform.""" self.logTestName() s = 0.543 S = controlled_phase(s) # test that S = R_2(pi/2) CX(s) R_2(pi/2)^\dagger R2 = block_diag(np.identity(2), rotation(np.pi/2))[:, [0, 2, 1, 3]][[0, 2, 1, 3]] expected = R2 @ controlled_addition(s) @ R2.conj().T self.assertAllAlmostEqual(S, expected, delta=self.tol) # test that S[x1, x2, p1, p2] -> [x1, x2, p1+sx2, p2+sx1] x1 = 0.5432 x2 = -0.453 p1 = 0.154 p2 = -0.123 out = S @ np.array([x1, x2, p1, p2])*np.sqrt(2*hbar) expected = np.array([x1, x2, p1+s*x2, p2+s*x1])*np.sqrt(2*hbar) self.assertAllAlmostEqual(out, expected, delta=self.tol)
def reference(*x): """reference circuit""" if callable(qop): # if the default.qubit is an operation accepting parameters, # initialise it using the parameters generated above. O = qop(*x) else: # otherwise, the operation is simply an array. O = qop # calculate the expected output if op.num_wires == 1 or g == "QubitUnitary": out_state = np.kron(O @ np.array([1, 0]), np.array([1, 0])) elif g == "QubitStateVector": out_state = x[0] elif g == "BasisState": out_state = np.array([0, 0, 0, 1]) else: out_state = O @ dev._state expectation = out_state.conj() @ np.kron(X, np.identity(2)) @ out_state return expectation
def test_arbitrary_rotation(self): """Test arbitrary single qubit rotation is correct""" self.logTestName() # test identity for theta=0 self.assertAllAlmostEqual(Rot3(0, 0, 0), np.identity(2), delta=self.tol) # expected result def arbitrary_rotation(x, y, z): """arbitrary single qubit rotation""" c = np.cos(y / 2) s = np.sin(y / 2) return np.array( [ [np.exp(-0.5j * (x + z)) * c, -np.exp(0.5j * (x - z)) * s], [np.exp(-0.5j * (x - z)) * s, np.exp(0.5j * (x + z)) * c], ] ) a, b, c = 0.432, -0.152, 0.9234 self.assertAllAlmostEqual( Rot3(a, b, c), arbitrary_rotation(a, b, c), delta=self.tol )
# Applying this, we can see that # # .. math:: # # H = 4 + 2I\otimes X + 4I \otimes Z - X\otimes X + 5 Y\otimes Y + 2Z\otimes X. # # To perform "doubly stochastic" gradient descent, we simply apply the stochastic # gradient descent approach from above, but in addition also uniformly sample # a subset of the terms for the Hamiltonian expectation at each optimization step. # This inserts another element of stochasticity into the system---all the while # convergence continues to be guaranteed! # # Let's create a QNode that randomly samples a single term from the above # Hamiltonian as the observable to be measured. I = np.identity(2) X = np.array([[0, 1], [1, 0]]) Y = np.array([[0, -1j], [1j, 0]]) Z = np.array([[1, 0], [0, -1]]) terms = np.array([ 2 * np.kron(I, X), 4 * np.kron(I, Z), -np.kron(X, X), 5 * np.kron(Y, Y), 2 * np.kron(Z, X) ]) @qml.qnode(dev_stochastic) def circuit(params, n=None): StronglyEntanglingLayers(weights=params, wires=[0, 1]) idx = np.random.choice(np.arange(5), size=n, replace=False) A = np.sum(terms[idx], axis=0)
class DummyDevice(DefaultGaussian): """Dummy device to allow Kerr operations""" _operation_map = DefaultGaussian._operation_map.copy() _operation_map['Kerr'] = lambda *x, **y: np.identity(2)
# --- Sanity check --- # @qml.qnode(device) # def qft_inv_test(input): # prepare_state(input) # qft() # iqft() # # basis_obs = qml.Hermitian(np.diag(range(2 ** n_qubits)), wires=range(n_qubits)) # return qml.sample(basis_obs) # # def test_qft(input): # samples = qft_inv_test(input=input).astype(int) # q_probs = np.bincount(samples, minlength=2 ** n_qubits) / n_shots # return q_probs # I16 = np.identity(2**4) # for i in range(2**4): # q_probs = test_qft(I16[i]) # print(q_probs) # --- Optimize: state -> QFT (using U) -> QFT (traditional) -> state def eigenvalues2basisvector(eigenvalues): n_basis_vector = sum( [2**n if i == -1 else 0 for n, i in enumerate(reversed(eigenvalues))]) return [ *([0] * n_basis_vector), 1, *([0] * (2**n_qubits - n_basis_vector - 1)) ]
############################################################################## # Comparison of quantum and classical results # ------------------------------------------- # # Since the specific problem considered in this tutorial has a small size, we can also # solve it in a classical way and then compare the results with our quantum solution. # ############################################################################## # Classical algorithm # ^^^^^^^^^^^^^^^^^^^ # To solve the problem in a classical way, we use the explicit matrix representation in # terms of numerical NumPy arrays. Id = np.identity(2) Z = np.array([[1, 0], [0, -1]]) X = np.array([[0, 1], [1, 0]]) A_0 = np.identity(8) A_1 = np.kron(np.kron(X, Z), Id) A_2 = np.kron(np.kron(X, Id), Id) A_num = c[0] * A_0 + c[1] * A_1 + c[2] * A_2 b = np.ones(8) / np.sqrt(8) ############################################################################## # We can print the explicit values of :math:`A` and :math:`b`: print("A = \n", A_num) print("b = \n", b)
def test_apply(self): """Test the application of gates to a state""" self.logTestName() # loop through all supported operations for gate_name, fn in self.dev._operation_map.items(): log.debug("\tTesting %s gate...", gate_name) self.dev.reset() # start in the displaced squeezed state alpha = 0.542 + 0.123j a = abs(alpha) phi_a = np.angle(alpha) r = 0.652 phi_r = -0.124 self.dev.apply('DisplacedSqueezedState', wires=[0], par=[a, phi_a, r, phi_r]) self.dev.apply('DisplacedSqueezedState', wires=[1], par=[a, phi_a, r, phi_r]) # 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 == 'GaussianState': p = [ np.array([0.432, 0.123, 0.342, 0.123]), np.diag([0.5234] * 4) ] w = list(range(2)) expected_out = p elif gate_name == 'Interferometer': w = list(range(2)) p = [U] S = fn(*p) expected_out = S @ self.dev._state[0], S @ self.dev._state[ 1] @ S.T else: # the parameter is a float p = [0.432423, -0.12312, 0.324, 0.751][:op.num_params] if gate_name == 'Displacement': alpha = p[0] * np.exp(1j * p[1]) state = self.dev._state mu = state[0].copy() mu[w[0]] += alpha.real * np.sqrt(2 * hbar) mu[w[0] + 2] += alpha.imag * np.sqrt(2 * hbar) expected_out = mu, state[1] elif 'State' in gate_name: mu, cov = fn(*p, hbar=hbar) expected_out = self.dev._state expected_out[0][[w[0], w[0] + 2]] = mu ind = np.concatenate( [np.array([w[0]]), np.array([w[0]]) + 2]) rows = ind.reshape(-1, 1) cols = ind.reshape(1, -1) expected_out[1][rows, cols] = cov else: # if the default.gaussian is an operation accepting parameters, # initialise it using the parameters generated above. S = fn(*p) # calculate the expected output if op.num_wires == 1: # reorder from symmetric ordering to xp-ordering S = block_diag( S, np.identity(2))[:, [0, 2, 1, 3]][[0, 2, 1, 3]] expected_out = S @ self.dev._state[0], S @ self.dev._state[ 1] @ S.T self.dev.apply(gate_name, wires=w, par=p) # verify the device is now in the expected state self.assertAllAlmostEqual(self.dev._state[0], expected_out[0], delta=self.tol) self.assertAllAlmostEqual(self.dev._state[1], expected_out[1], delta=self.tol)