def test_unwrap_autograd(): """Test that unwrapping a tape with Autograd parameters works as expected""" from pennylane import numpy as anp p = [ anp.tensor(0.1, requires_grad=True), anp.tensor(0.2, requires_grad=False), 0.5, anp.tensor(0.3, requires_grad=True), ] with qml.tape.QuantumTape() as tape: qml.RX(p[0], wires=0) qml.RY(p[1], wires=0) qml.PhaseShift(p[2], wires=0) qml.RZ(p[3], wires=0) with tape.unwrap() as unwrapped_tape: # inside the context manager, all parameters # will be unwrapped to NumPy arrays params = tape.get_parameters(trainable_only=False) assert all(isinstance(i, float) for i in params) assert np.allclose(params, [0.1, 0.2, 0.5, 0.3]) assert tape.trainable_params == {0, 2, 3} # outside the context, the original parameters have been restored. assert tape.get_parameters(trainable_only=False) == p
def test_unwrap_autograd_backward(): """Test that unwrapping a tape with Autograd parameters works as expected during a backwards pass""" from pennylane import numpy as anp from autograd.numpy.numpy_boxes import ArrayBox p = [ anp.tensor([0.1, 0.5, 0.3], requires_grad=True), anp.tensor(0.2, requires_grad=False), ] def cost(*p): with qml.tape.QuantumTape() as tape: qml.RX(p[0][0], wires=0) qml.RY(p[1], wires=0) qml.PhaseShift(p[0][1], wires=0) qml.RZ(p[0][2], wires=0) with tape.unwrap() as unwrapped_tape: # inside the context manager, all parameters # will be unwrapped to NumPy arrays params = tape.get_parameters(trainable_only=False) assert all(isinstance(i, float) for i in params) assert np.allclose(params, [0.1, 0.2, 0.5, 0.3]) assert tape.trainable_params == {0, 2, 3} # outside the context, the original parameters have been restored. params = tape.get_parameters(trainable_only=False) assert any(isinstance(i, ArrayBox) for i in params) return p[0][0] * p[1]**2 * anp.sin(p[0][1]) * anp.exp(-0.5 * p[0][2]) qml.grad(cost)(*p) qml.jacobian(qml.grad(cost))(*p)
def test_two_trainable_args(self, opt, opt_name, tol): """Tests that a cost function that takes at least two trainable arguments executes well.""" dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x, y): qml.RX(x, wires=0) qml.RX(y, wires=0) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) def cost(x, y, target): return (circuit(x, y) - target)**2 ev = np.tensor(0.7781, requires_grad=False) x = np.tensor(0.0, requires_grad=True) y = np.tensor(0.0, requires_grad=True) original_ev = ev (x, y, ev), cost = opt.step_and_cost(cost, x, y, ev) # check that the argument to RX doesn't change, as the X rotation doesn't influence <Z> assert x == 0 assert ev == original_ev
def test_passing_requires_grad_arg(self): """Test that you can instantiate the Tensor class with the requires_grad argument""" # default value is true x = np.tensor([0, 1, 2]) assert x.requires_grad x = np.tensor([0, 1, 2], requires_grad=True) assert x.requires_grad x = np.tensor([0, 1, 2], requires_grad=False) assert not x.requires_grad
def _execute( parameters, tapes=None, device=None, execute_fn=None, gradient_fn=None, gradient_kwargs=None, _n=1, max_diff=2, ): # pylint: disable=dangerous-default-value,unused-argument """Autodifferentiable wrapper around ``Device.batch_execute``. The signature of this function is designed to work around Autograd restrictions. Note that the ``parameters`` argument is dependent on the ``tapes`` argument; this function should always be called as follows: >>> parameters = [autograd.builtins.list(t.get_parameters()) for t in tapes]) >>> parameters = autograd.builtins.tuple(parameters) >>> _execute(parameters, tapes=tapes, device=device) In particular: - ``parameters`` is dependent on the provided tapes: always extract them as above - ``tapes`` is a *required* argument - ``device`` is a *required* argument The private argument ``_n`` is used to track nesting of derivatives, for example if the nth-order derivative is requested. Do not set this argument unless you understand the consequences! """ with qml.tape.Unwrap(*tapes): res, jacs = execute_fn(tapes, **gradient_kwargs) for i, r in enumerate(res): if isinstance(res[i], np.ndarray): # For backwards compatibility, we flatten ragged tape outputs # when there is no sampling r = np.hstack( res[i]) if res[i].dtype == np.dtype("object") else res[i] res[i] = np.tensor(r) elif isinstance(res[i], tuple): res[i] = tuple(np.tensor(r) for r in res[i]) else: res[i] = qml.math.toarray(res[i]) return res, jacs
def test_multiple_gate_parameter(self): """Test that when supplied a PennyLane tensor, a QNode passes arguments as unwrapped tensors to a gate taking multiple parameters""" dev = qml.device("qiskit.aer", wires=1) @qml.qnode(dev) def circuit(phi=None): for idx, x in enumerate(phi): qml.Rot(*x, wires=idx) return qml.expval(qml.PauliZ(0)) phi = tensor([[0.04439891, 0.14490549, 3.29725643]]) with qml._queuing.OperationRecorder() as rec: circuit(phi=phi) # Test the rotation applied assert rec.queue[0].name == "Rot" assert len(rec.queue[0].parameters) == 3 # Test that the gate parameters are not PennyLane tensors, # but are instead floats assert not isinstance(rec.queue[0].parameters[0], tensor) assert isinstance(rec.queue[0].parameters[0], float) assert not isinstance(rec.queue[0].parameters[1], tensor) assert isinstance(rec.queue[0].parameters[1], float) assert not isinstance(rec.queue[0].parameters[2], tensor) assert isinstance(rec.queue[0].parameters[2], float)
def test_extra_parameters_were_passed(self, recorder): """Tests that loading raises an error when extra parameters were passed.""" theta = Parameter("θ") phi = Parameter("φ") x = np.tensor(0.5, requires_grad=False) y = np.tensor(0.3, requires_grad=False) qc = QuantumCircuit(3, 1) quantum_circuit = load(qc) with pytest.raises(QiskitError): with recorder: quantum_circuit(params={theta: x, phi: y})
def test_single_gate_parameter(self, monkeypatch): """Test that when supplied a PennyLane tensor, a QNode passes an unwrapped tensor as the argument to a gate taking a single parameter""" dev = qml.device("qiskit.aer", wires=4) @qml.qnode(dev) def circuit(phi=None): for y in phi: for idx, x in enumerate(y): qml.RX(x, wires=idx) return qml.expval(qml.PauliZ(0)) phi = tensor([[0.04439891, 0.14490549, 3.29725643, 2.51240058]]) with qml._queuing.OperationRecorder() as rec: circuit(phi=phi) for i in range(phi.shape[1]): # Test each rotation applied assert rec.queue[0].name == "RX" assert len(rec.queue[0].parameters) == 1 # Test that the gate parameter is not a PennyLane tensor, but a # float assert not isinstance(rec.queue[0].parameters[0], tensor) assert isinstance(rec.queue[0].parameters[0], float)
def test_requires_grad_setter(self): """Test that the value of requires_grad can be changed on an instantiated object""" # default value is true x = np.tensor([0, 1, 2]) assert x.requires_grad x.requires_grad = False assert not x.requires_grad
def test_string_representation(self, capsys): """Test the string representation is correct""" x = np.tensor([0, 1, 2]) print(x.__repr__()) captured = capsys.readouterr() assert "tensor([0, 1, 2], requires_grad=True)" in captured.out x.requires_grad = False print(x.__repr__()) captured = capsys.readouterr() assert "tensor([0, 1, 2], requires_grad=False)" in captured.out
def test_indexing(self, grad): """Test that indexing into a tensor always returns a tensor""" x = np.tensor([[0, 1, 2], [3, 4, 5]], requires_grad=grad) assert isinstance(x[0], np.tensor) assert x[0].requires_grad is grad assert isinstance(x[0, 0], np.tensor) assert x[0, 0].requires_grad is grad assert x[0, 0].shape == tuple() assert x[0, 0].item() == 0
def test_one_non_trainable_one_trainable(self, opt): """Tests that a cost function that takes one non-trainable and one trainable parameter executes well.""" dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) def cost(target, x): # Note: the order of the arguments has been swapped return (circuit(x) - target) ** 2 ev = np.tensor(0.7781, requires_grad=False) x = np.tensor(0.0, requires_grad=True) original_ev = ev (ev, x), cost = opt.step_and_cost(cost, ev, x) # check that the argument to RX doesn't change, as the X rotation doesn't influence <Z> assert x == 0 assert ev == original_ev
def test_multiple_gate_parameter(self): """Test that a QNode handles passing multiple tensor objects to the same gate without an error""" dev = qml.device("qiskit.aer", wires=1) @qml.qnode(dev) def circuit(phi=None): for idx, x in enumerate(phi): qml.Rot(*x, wires=idx) return qml.expval(qml.PauliZ(0)) phi = tensor([[0.04439891, 0.14490549, 3.29725643]]) circuit(phi=phi)
def test_quantum_circuit_error_passing_parameters_not_required(self, recorder): """Tests the load method raises a QiskitError if arguments that are not required were passed.""" theta = Parameter("θ") angle = np.tensor(0.5, requires_grad=False) qc = QuantumCircuit(3, 1) qc.z([0]) quantum_circuit = load(qc) with pytest.raises(QiskitError): with recorder: quantum_circuit(params={theta: angle})
def test_quantum_circuit_error_by_passing_wrong_parameters(self, recorder): """Tests the load method for a QuantumCircuit raises a QiskitError, if the wrong type of arguments were passed.""" theta = Parameter("θ") angle = np.tensor("some_string_instead_of_an_angle", requires_grad=False) qc = QuantumCircuit(3, 1) qc.rz(theta, [0]) quantum_circuit = load(qc) with pytest.raises(QiskitError): with recorder: quantum_circuit(params={theta: angle})
def test_tensor_gradient_no_error(self, monkeypatch): """Tests that the gradient calculation of a circuit that contains a RandomLayers template taking a PennyLane tensor as differentiable argument executes without error. """ dev = qml.device("default.qubit", wires=4) @qml.qnode(dev) def circuit(phi): # Random quantum circuit RandomLayers(phi, wires=list(range(4))) return qml.expval(qml.PauliZ(0)) phi = tensor([[0.04439891, 0.14490549, 3.29725643, 2.51240058]]) qml.jacobian(circuit)(phi)
def test_multiple_gate_parameter(self): """Test that when supplied a PennyLane tensor, a QNode passes arguments as unwrapped tensors to a gate taking multiple parameters""" dev = qml.device("default.qubit", wires=1) @qml.qnode(dev, diff_method="parameter-shift") def circuit(phi=None): for idx, x in enumerate(phi): qml.Rot(*x, wires=idx) return qml.expval(qml.PauliZ(0)) phi = np.tensor([[0.04439891, 0.14490549, 3.29725643]]) with qml._queuing.OperationRecorder() as rec: circuit(phi=phi) # Test the rotation applied assert rec.queue[0].name == "Rot" assert len(rec.queue[0].parameters) == 3
def test_tracker(self): """Tests the device tracker with batch execution.""" dev = qml.device('qiskit.aer', shots=100, wires=3) @qml.qnode(dev, diff_method="parameter-shift") def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) x = tensor(0.1, requires_grad=True) with qml.Tracker(dev) as tracker: qml.grad(circuit)(x) expected = {'executions': [1, 1, 1], 'shots': [100, 100, 100], 'batches': [1, 1], 'batch_len': [1, 2]} assert tracker.history == expected
def test_single_gate_parameter(self, monkeypatch): """Test that when supplied a PennyLane tensor, a QNode passes an unwrapped tensor as the argument to a gate taking a single parameter""" dev = qml.device("default.qubit", wires=4) @qml.qnode(dev, diff_method="parameter-shift") def circuit(phi=None): for y in phi: for idx, x in enumerate(y): qml.RX(x, wires=idx) return qml.expval(qml.PauliZ(0)) phi = np.tensor([[0.04439891, 0.14490549, 3.29725643, 2.51240058]]) with qml.tape.OperationRecorder() as rec: circuit(phi=phi) for i in range(phi.shape[1]): # Test each rotation applied assert rec.queue[0].name == "RX" assert len(rec.queue[0].parameters) == 1
def test_tensor_unwrapped(self, monkeypatch): """Test that the random_layer() function used by RandomLayers passes a float value to its gate after successfully unwrapping a one element PennyLane tensor. The test involves using RandomLayers, which then calls random_layer internally. Eventually each gate used by random_layer receives a single scalar. In this test case, this is done by indexing into a PennyLane tensor two times. """ dev = qml.device("default.qubit", wires=4) lst = [] # Mock function that accumulates gate parameters mock_func = lambda par, wires: lst.append(par) with monkeypatch.context() as m: # Mock the gates used in RandomLayers m.setattr(pennylane.templates.layers.random, "RX", mock_func) m.setattr(pennylane.templates.layers.random, "RY", mock_func) m.setattr(pennylane.templates.layers.random, "RZ", mock_func) @qml.qnode(dev) def circuit(phi=None): # Random quantum circuit RandomLayers(phi, wires=list(range(4))) return qml.expval(qml.PauliZ(0)) phi = tensor([[0.04439891, 0.14490549, 3.29725643, 2.51240058]]) # Call the QNode, accumulate parameters circuit(phi=phi) # Check parameters assert all([isinstance(x, float) for x in lst])
def astensor(tensor): return np.tensor(tensor)
class AutogradBox(qml.math.TensorBox): """Implements the :class:`~.TensorBox` API for ``pennylane.numpy`` tensors. For more details, please refer to the :class:`~.TensorBox` documentation. """ abs = wrap_output(lambda self: np.abs(self.data)) angle = wrap_output(lambda self: np.angle(self.data)) arcsin = wrap_output(lambda self: np.arcsin(self.data)) cast = wrap_output(lambda self, dtype: np.tensor(self.data, dtype=dtype)) diag = staticmethod(wrap_output(lambda values, k=0: np.diag(values, k=k))) expand_dims = wrap_output( lambda self, axis: np.expand_dims(self.data, axis=axis)) ones_like = wrap_output(lambda self: np.ones_like(self.data)) reshape = wrap_output(lambda self, shape: np.reshape(self.data, shape)) sqrt = wrap_output(lambda self: np.sqrt(self.data)) sum = wrap_output(lambda self, axis=None, keepdims=False: np.sum( self.data, axis=axis, keepdims=keepdims)) T = wrap_output(lambda self: self.data.T) squeeze = wrap_output(lambda self: self.data.squeeze()) @staticmethod def astensor(tensor): return np.tensor(tensor) @staticmethod @wrap_output def concatenate(values, axis=0): return np.concatenate(AutogradBox.unbox_list(values), axis=axis) @staticmethod @wrap_output def dot(x, y): x, y = AutogradBox.unbox_list([x, y]) if x.ndim == 0 and y.ndim == 0: return x * y if x.ndim == 2 and y.ndim == 2: return x @ y return np.dot(x, y) @property def interface(self): return "autograd" def numpy(self): if hasattr(self.data, "_value"): # Catches the edge case where the data is an Autograd arraybox, # which only occurs during backpropagation. return self.data._value return self.data.numpy() @property def requires_grad(self): return self.data.requires_grad @wrap_output def scatter_element_add(self, index, value): size = self.data.size flat_index = np.ravel_multi_index(index, self.shape) t = [0] * size t[flat_index] = value self.data = self.data + np.array(t).reshape(self.shape) return self.data @property def shape(self): return self.data.shape @staticmethod @wrap_output def stack(values, axis=0): return np.stack(AutogradBox.unbox_list(values), axis=axis) @wrap_output def take(self, indices, axis=None): indices = self.astensor(indices) if axis is None: return self.data.flatten()[indices] fancy_indices = [slice(None)] * axis + [indices] return self.data[tuple(fancy_indices)] @staticmethod @wrap_output def where(condition, x, y): return np.where(condition, *AutogradBox.unbox_list([x, y]))
def cast(self, dtype): return AutogradBox(np.tensor(self.data, dtype=dtype))
plt.scatter(X_test[np.where(y_train == 1)[0],0], X_test[np.where(y_train == 1)[0],1], color="b", marker="x", label="test, 1") plt.scatter(X_test[np.where(y_train == -1)[0],0], X_test[np.where(y_train == -1)[0],1], color="r", marker="x", label="test, -1") plt.ylim([0, 1]) plt.xlim([0, 1]) plt.legend() X = X_train opt_param = np.tensor([[[ 6.22961793, 6.1909463 , 6.24821366, 1.88800397, 1.6515437 ], [-3.50578116, 2.87429701, -0.55558014, -2.97461847, 4.3646466 ]], [[ 4.59893525, -0.01877453, 4.86909045, 1.61046237, 4.3342154 ], [ 6.54969706, 0.76974914, 6.13216135, 3.19770538, 0.35820405]], [[-0.06825097, 5.46138114, -0.38685812, 2.62531926, 5.94363286], [ 3.84330489, 7.62532526, 3.31992264, 4.53318486, 2.90021471]], [[ 3.27271762, 6.284331 , -0.0095848 , 1.71022713, 1.72119449], [ 5.26413732, -0.5363315 , 0.02694912, 1.85543017, 0.09469438]], [[ 1.61977233, 2.12403094, 1.52887576, 1.87843468, 5.10722657], [ 1.83547388, 0.10519713, -0.14516422, 2.34971729, -0.15396484]], [[ 1.15227788, 4.42815449, 4.77992685, 2.00495827, 4.68944624], [ 1.90477385, -0.22817579, 6.21664772, 0.34922366, 6.44687527]], [[ 4.47834114, 5.80827321, 4.8221783 , 2.07389821, 0.40258912], [ 6.07380714, 6.33676481, 6.17787822, 1.86149763, 6.59189267]], [[ 5.56242829, 4.49153866, 3.66496649, 4.76465886, 0.80552847], [ 3.36765317, 3.41585518, 1.40441779, 1.24372229, 5.85030332]]], requires_grad=True) if use_trained_params: param = np.copy(opt_param) print(f"Using trained parameters with average magnitude {np.mean(np.abs(param))}") else: np.random.seed(43) param = np.random.random(size=opt_param.shape) * 4 * np.pi - 2 * np.pi print(f"Using untrained parameters with average magnitude {np.mean(np.abs(param))}")
# Amplitude damping projects a state to :math:`|0\rangle` with probability :math:`p` and # otherwise leaves it unchanged. It is # described by the Kraus operators # # .. math:: # # K_0 = \begin{pmatrix}1 & 0\\ 0 & \sqrt{1-p}\end{pmatrix}, \quad # K_1 = \begin{pmatrix}0 & \sqrt{p}\\ 0 & 0\end{pmatrix}. # # What damping parameter (:math:`p`) explains the experimental outcome? We can answer this question # by optimizing the channel parameters to reproduce the experimental # observation! 💪 Since the parameter :math:`p` is a probability, we use a sigmoid function to # ensure that the trainable parameters give rise to a valid channel parameter, i.e., a number # between 0 and 1. # ev = np.tensor([0.7781], requires_grad=False) # observed expectation value def sigmoid(x): return 1 / (1 + np.exp(-x)) @qml.qnode(dev) def damping_circuit(x): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) qml.AmplitudeDamping(sigmoid(x), wires=0) # p = sigmoid(x) qml.AmplitudeDamping(sigmoid(x), wires=1) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))