def test_wrapped_function_nontrainable_variable_args(self): """Test that a wrapped function with signature of the form func(arr1, arr2, ...) acting on non-trainable input returns non-trainable output""" arr1 = np.array([0, 1], requires_grad=False) arr2 = np.array([2, 3], requires_grad=False) res = np.arctan2(arr1, arr2) assert not res.requires_grad # If one of the inputs is trainable, the output always is. arr1.requires_grad = True res = np.arctan2(arr1, arr2) assert res.requires_grad
def test_controlled_addition(self): """Test the CX symplectic transform.""" self.logTestName() s = 0.543 S = controlled_addition(s) # test that S = B(theta+pi/2, 0) [S(z) x S(-z)] B(theta, 0) r = np.arcsinh(-s / 2) theta = 0.5 * np.arctan2(-1 / np.cosh(r), -np.tanh(r)) Sz = block_diag(squeezing(r, 0), squeezing(-r, 0))[:, [0, 2, 1, 3]][[0, 2, 1, 3]] expected = beamsplitter(theta + np.pi / 2, 0) @ Sz @ beamsplitter( theta, 0) self.assertAllAlmostEqual(S, expected, delta=self.tol) # test that S[x1, x2, p1, p2] -> [x1, x2+sx1, p1-sp2, p2] 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 + s * x1, p1 - s * p2, p2]) * np.sqrt( 2 * hbar) self.assertAllAlmostEqual(out, expected, delta=self.tol)
def _rotosolve(objective_fn, x, generators, d): r"""The rotosolve step for one parameter and one set of generators. Updates the parameter :math:`\theta_d` based on Equation 1 in `Ostaszewski et al. (2019) <https://arxiv.org/abs/1905.09692>`_. Args: objective_fn (function): The objective function for optimization. It must have the signature ``objective_fn(x, generators=None)`` with a sequence of the values ``x`` and a list of the gates ``generators`` as inputs, returning a single value. x (Union[Sequence[float], float]): Sequence containing the initial values of the variables to be optimized over, or a single float with the initial value. generators (list[~.Operation]): List containing the initial ``pennylane.ops.qubit`` operators to be used in the circuit and optimized over. d (int): The position in the input sequence ``x`` containing the value to be optimized. Returns: array: The input sequence ``x`` with the value at position ``d`` optimized. """ # helper function for x[d] = theta def insert(x, d, theta): x[d] = theta return x H_0 = float(objective_fn(insert(x, d, 0), generators)) H_p = float(objective_fn(insert(x, d, np.pi / 2), generators)) H_m = float(objective_fn(insert(x, d, -np.pi / 2), generators)) a = np.arctan2(2 * H_0 - H_p - H_m, H_p - H_m) x[d] = -np.pi / 2 - a if x[d] <= -np.pi: x[d] += 2 * np.pi return x
def _rotosolve(objective_fn, x, d): r"""The rotosolve step for one parameter. Updates the parameter :math:`\theta_d` based on Equation 1 in `Ostaszewski et al. (2019) <https://arxiv.org/abs/1905.09692>`_. Args: objective_fn (function): The objective function for optimization. It should take a sequence of the values ``x`` and a list of the gates ``generators`` as inputs, and return a single value. x (Union[Sequence[float], float]): Sequence containing the initial values of the variables to be optimized over, or a single float with the initial value. d (int): The position in the input sequence ``x`` containing the value to be optimized. Returns: array: The input sequence ``x`` with the value at position ``d`` optimized. """ # helper function for x[d] = theta def insert(x, d, theta): x[d] = theta return x H_0 = float(objective_fn(insert(x, d, 0))) H_p = float(objective_fn(insert(x, d, np.pi / 2))) H_m = float(objective_fn(insert(x, d, -np.pi / 2))) a = np.arctan2(2 * H_0 - H_p - H_m, H_p - H_m) x[d] = -np.pi / 2 - a if x[d] <= -np.pi: x[d] += 2 * np.pi return x
def rotosolve(d, params, generators, cost, M_0): # M_0 only calculated once params[d] = np.pi / 2.0 M_0_plus = cost(params, generators) params[d] = -np.pi / 2.0 M_0_minus = cost(params, generators) a = np.arctan2(2.0 * M_0 - M_0_plus - M_0_minus, M_0_plus - M_0_minus) # returns value in (-pi,pi] params[d] = -np.pi / 2.0 - a if params[d] <= -np.pi: params[d] += 2 * np.pi return cost(params, generators)
def opt_theta(d, params, cost): params[d] = 0.0 M_0 = cost(params) params[d] = np.pi / 2.0 M_0_plus = cost(params) params[d] = -np.pi / 2.0 M_0_minus = cost(params) a = np.arctan2(2.0 * M_0 - M_0_plus - M_0_minus, M_0_plus - M_0_minus) # returns value in (-pi,pi] params[d] = -np.pi / 2.0 - a # restrict output to lie in (-pi,pi], a convention # consistent with the Rotosolve paper if params[d] <= -np.pi: params[d] += 2 * np.pi
def QNNLayer(self, params): ''' Definition of a single ST step for layering QNN ''' ExcInts = params[0:3] ExtField = params[3:6] # Parameters for external # field evol Hx = ExtField[0] Hy = ExtField[1] Hz = ExtField[2] H = np.sqrt(Hx**2 + Hy**2 + Hz**2) # Parameter values for Qiskit PHI = np.arctan2(Hy, Hx) + 2*np.pi THETA = np.arccos(Hz/H) LAMBDA = np.pi # Cascade Spin pair interaction for idx in range(self.num_spins-1): # Convert to computational basis qml.CNOT(wires=[idx, idx+1]) qml.Hadamard(wires=idx) # Compute J3 phase qml.RZ(ExcInts[2], wires=idx+1) # Compute J1 phase qml.RZ(ExcInts[0], wires=idx) # Compute J2 Phase qml.CNOT(wires=[idx, idx+1]) qml.RZ(-ExcInts[1], wires=idx+1) qml.CNOT(wires=[idx, idx+1]) # Return to computational basis qml.Hadamard(wires=idx) qml.CNOT(wires=[idx, idx+1]) # Include external field qml.U3(-THETA, -LAMBDA, -PHI, wires=idx) qml.RZ(H, wires=idx) qml.U3(THETA, PHI, LAMBDA, wires=idx) # Include external field for last spin qml.U3(-THETA, -LAMBDA, -PHI, wires=self.num_spins-1) qml.RZ(H, wires=self.num_spins-1) qml.U3(THETA, PHI, LAMBDA, wires=self.num_spins-1)
def rotosolve_step(f, x): """Helper function to test the Rotosolve and Rotoselect optimizers""" # make sure that x is an array if np.ndim(x) == 0: x = np.array([x]) # helper function for x[d] = theta def insert(x, d, theta): x[d] = theta return x for d, _ in enumerate(x): H_0 = float(f(insert(x, d, 0))) H_p = float(f(insert(x, d, np.pi / 2))) H_m = float(f(insert(x, d, -np.pi / 2))) a = np.arctan2(2 * H_0 - H_p - H_m, H_p - H_m) x[d] = -np.pi / 2 - a if x[d] <= -np.pi: x[d] += 2 * np.pi return x