def tape_mode(request, mocker): """Tests using this fixture will be run twice, once in tape mode and once without.""" if request.param: # Several attributes and methods on the old QNode have a new location on the new QNode/tape. # Here, we dynamically mock so that the tests do not have to be modified to support both # tape and non-tape mode. Once tape mode is default, we can make the equivalent # changes directly in the tests. mocker.patch( "pennylane.tape.QNode.ops", property( lambda self: self.qtape.operations + self.qtape.observables), create=True) mocker.patch("pennylane.tape.QNode.h", property(lambda self: self.diff_options["h"]), create=True) mocker.patch("pennylane.tape.QNode.order", property(lambda self: self.diff_options["order"]), create=True) mocker.patch("pennylane.tape.QNode.jacobian", lambda self: self.qtape.jacobian, create=True) qml.enable_tape() yield if request.param: qml.disable_tape()
def test_metric_tensor_tape_mode(self): """Test that the metric tensor can be calculated in tape mode, and that it is equal to a metric tensor calculated in non-tape mode.""" if not qml.tape_mode_active(): pytest.skip("This test is only intended for tape mode") dev = qml.device("default.qubit", wires=2) p = np.array([1., 1., 1.]) def ansatz(params, **kwargs): qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) qml.CNOT(wires=[0, 1]) qml.PhaseShift(params[2], wires=1) h = qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliZ(1)]) qnodes = qml.ExpvalCost(ansatz, h, dev) mt = qml.metric_tensor(qnodes)(p) assert qml.tape_mode_active() # Check that tape mode is still active qml.disable_tape() @qml.qnode(dev) def circuit(params): qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) qml.CNOT(wires=[0, 1]) qml.PhaseShift(params[2], wires=1) return qml.expval(qml.PauliZ(0)) mt2 = circuit.metric_tensor([p]) assert np.allclose(mt, mt2)
def test_enable_tape_mode_class(): """Test that the enable_tape function properly enables tape mode when creating QNodes using the class.""" dev = qml.device("default.qubit", wires=1) def circuit(x, y): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) qnode = qml.QNode(circuit, dev) qnode(0.5, 0.1) assert not isinstance(qnode, qml.tape.QNode) assert isinstance(qnode, qml.qnodes.BaseQNode) assert not hasattr(qnode, "qtape") qml.enable_tape() def circuit(x, y): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) qnode = qml.QNode(circuit, dev) qnode(0.5, 0.1) assert isinstance(qnode, qml.tape.QNode) assert not isinstance(qnode, qml.qnodes.BaseQNode) assert hasattr(qnode, "qtape") qml.disable_tape()
def tape_mode(request): """Tests using this fixture will be run twice, once in tape mode and once without.""" if request.param: qml.enable_tape() yield if request.param: qml.disable_tape()
def test_disable_tape_exception(): """Test that disabling tape mode raises a warning if not currently in tape mode""" qml.disable_tape() with pytest.warns(UserWarning, match="Tape mode is not currently enabled"): qml.disable_tape() qml.enable_tape()
def test_tape_mode_detection(): """Test that the function `tape_mode_active` returns True only if tape mode is activated.""" qml.disable_tape() assert not qml.tape_mode_active() qml.enable_tape() assert qml.tape_mode_active()
def test_enable_tape_mode_decorator(): """Test that the enable_tape function properly enables tape mode when creating QNodes using the decorator.""" dev = qml.device("default.qubit", wires=1) qml.disable_tape() @qml.qnode(dev) def circuit(x, y): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) circuit(0.5, 0.1) assert not isinstance(circuit, qml.tape.QNode) assert isinstance(circuit, qml.qnodes.BaseQNode) assert not hasattr(circuit, "qtape") qml.enable_tape() assert "tape" in qml.expval.__module__ @qml.qnode(dev) def circuit(x, y): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) circuit(0.5, 0.1) assert isinstance(circuit, qml.tape.QNode) assert not isinstance(circuit, qml.qnodes.BaseQNode) assert hasattr(circuit, "qtape")
def test_disable_tape(): """Test that the disable_tape function reverts QNode creation to standard behaviour""" dev = qml.device("default.qubit", wires=1) def circuit(x, y): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) # doesn't matter how many times we call it qml.enable_tape() qml.enable_tape() qnode = qml.QNode(circuit, dev) qnode(0.5, 0.1) assert isinstance(qnode, qml.tape.QNode) assert not isinstance(qnode, qml.qnodes.BaseQNode) assert hasattr(qnode, "qtape") qml.disable_tape() qnode = qml.QNode(circuit, dev) qnode(0.5, 0.1) assert not isinstance(qnode, qml.tape.QNode) assert isinstance(qnode, qml.qnodes.BaseQNode) assert not hasattr(qnode, "qtape")
def test_identity_single_batched(self, dev): """Test computing the expectation value of the identity for a single return value.""" qml.enable_tape() dev = qml.device(dev, wires=1) with qml.tape.QuantumTape() as tape1: qml.expval(qml.Identity(wires=[0])) res = dev.batch_execute([tape1]) assert len(res) == 1 assert np.allclose(res[0], np.array([1])) qml.disable_tape()
def test_batch_exec(self, keep, tmpdir, monkeypatch, test_batch_result): """Test that the batch_execute method returns the desired result and that the result preserves the order in which circuits were submitted.""" qml.enable_tape() dev = qml.device("orquestra.forest", wires=3, keep_files=keep) with qml.tape.QuantumTape() as tape1: qml.expval(qml.PauliZ(wires=[0])) with qml.tape.QuantumTape() as tape2: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.expval(qml.PauliZ(wires=[0])) with qml.tape.QuantumTape() as tape3: qml.RX(0.432, wires=0) qml.expval(qml.PauliZ(wires=[0])) circuits = [tape1, tape2, tape3] test_uuid = "1234" assert not os.path.exists(tmpdir.join(f"expval-{test_uuid}-0.yaml")) with monkeypatch.context() as m: m.setattr(pennylane_orquestra.cli_actions, "user_data_dir", lambda *args: tmpdir) # Disable submitting to the Orquestra platform by mocking Popen m.setattr(subprocess, "Popen", lambda *args, **kwargs: MockPopen()) m.setattr( pennylane_orquestra.orquestra_device, "loop_until_finished", lambda *args, **kwargs: test_batch_result, ) # Disable random uuid generation m.setattr(uuid, "uuid4", lambda *args: test_uuid) res = dev.batch_execute(circuits) # Correct order of results is expected assert np.allclose(res[0], test_batch_res0) assert np.allclose(res[1], test_batch_res1) assert np.allclose(res[2], test_batch_res2) file_kept = os.path.exists( tmpdir.join(f"expval-{test_uuid}-0.yaml")) assert file_kept if keep else not file_kept qml.disable_tape()
def test_identity_multiple_tape(self, dev, tmpdir, monkeypatch): """Test computing the expectation value of the identity for multiple return values.""" qml.enable_tape() dev = qml.device(dev, wires=2, keep_files=False) with qml.tape.QuantumTape() as tape1: qml.RX(0.133, wires=0) qml.expval(qml.Identity(wires=[0])) with qml.tape.QuantumTape() as tape2: qml.RX(0.432, wires=0) qml.expval(qml.Identity(wires=[0])) qml.expval(qml.Identity(wires=[1])) circuits = [tape1, tape2] test_uuid = "1234" with monkeypatch.context() as m: m.setattr(pennylane_orquestra.cli_actions, "user_data_dir", lambda *args: tmpdir) # Disable submitting to the Orquestra platform by mocking Popen m.setattr(subprocess, "Popen", lambda *args, **kwargs: MockPopen()) m.setattr( pennylane_orquestra.orquestra_device, "loop_until_finished", lambda *args, **kwargs: None, ) # Disable random uuid generation m.setattr(uuid, "uuid4", lambda *args: test_uuid) res = dev.batch_execute(circuits) # No workflow files were created because we only computed with # identities assert not os.path.exists(tmpdir.join(f"expval-{test_uuid}.yaml")) assert not os.path.exists(tmpdir.join(f"expval-{test_uuid}.yaml")) expected = [ np.ones(1), np.ones(2), ] for r, e in zip(res, expected): assert np.allclose(r, e) qml.disable_tape()
def tape_mode(request, mocker): """Tests using this fixture will be run twice, once in tape mode and once without.""" if request.param: # Several attributes and methods on the old QNode have a new location on the new QNode/tape. # Here, we dynamically mock so that the tests do not have to be modified to support both # tape and non-tape mode. Once tape mode is default, we can make the equivalent # changes directly in the tests. mocker.patch( "pennylane.tape.QNode.ops", property( lambda self: self.qtape.operations + self.qtape.observables), create=True, ) mocker.patch("pennylane.tape.QNode.h", property(lambda self: self.diff_options["h"]), create=True) mocker.patch( "pennylane.tape.QNode.order", property(lambda self: self.diff_options["order"]), create=True, ) mocker.patch("pennylane.tape.QNode.circuit", property(lambda self: self.qtape.graph), create=True) def patched_jacobian(self, args, **kwargs): # pylint: disable=unused-argument method = kwargs.get("method", "best") if method == "A": method = "analytic" elif method == "F": method = "numeric" kwargs["method"] = method dev = kwargs["options"]["device"] return self.qtape.jacobian(dev, **kwargs) mocker.patch("pennylane.tape.QNode.jacobian", patched_jacobian, create=True) qml.enable_tape() yield if request.param: qml.disable_tape()
def test_draw_transform_raises(self): qml.disable_tape() dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, interface="autograd") def circuit(p1, p2, **kwargs): qml.RX(p1, wires=0) qml.RY(p2[0] * p2[1], wires=1) qml.RX(kwargs["p3"], wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)) with pytest.raises(ValueError, match="only works when tape mode is enabled"): result = draw(circuit, charset="ascii")
def test_jacobian_with_batch_execute(self): """Test that the value of the jacobian computed using the internal batch_execute method corresponds to the value computed with the default.qubit device. There are ``qubits * layers * 3 * 2`` many circuits to evaluate. """ try_resp = qe_list_workflow() need_login_msg = "token has expired, please log in again\n" if need_login_msg in try_resp: pytest.skip("Has not logged in to the Orquestra platform.") qml.enable_tape() # Evaluate 12 circuits (2 * 1 * 3 * 2) # By default, this fits into two separate workflow files qubits = 2 layers = 1 weights = qml.init.strong_ent_layers_uniform(layers, qubits) dev1 = qml.device( "orquestra.qiskit", backend="statevector_simulator", wires=qubits, analytic=True, keep_files=False, ) dev2 = qml.device("default.qubit", wires=qubits, analytic=True) def func(weights): qml.templates.StronglyEntanglingLayers(weights, wires=range(qubits)) return qml.expval(qml.PauliZ(0)) orquestra_qnode = qml.QNode(func, dev1) default_qnode = qml.QNode(func, dev2) dfunc1 = qml.grad(orquestra_qnode) dfunc2 = qml.grad(default_qnode) res_orquestra = dfunc1(weights) res_default_qubit = dfunc2(weights) assert np.allclose(res_orquestra, res_default_qubit) qml.disable_tape()
def test_error_if_not_expval_batched(self): """Test that an error is raised if not an expectation value is computed during batched execution""" qml.enable_tape() dev = qml.device("orquestra.qiskit", wires=2) with qml.tape.QuantumTape() as tape1: qml.expval(qml.PauliZ(wires=[0])) qml.var(qml.PauliZ(wires=[0])) with qml.tape.QuantumTape() as tape2: qml.expval(qml.PauliZ(wires=[0])) circuits = [tape1, tape2] with pytest.raises(NotImplementedError): res = dev.batch_execute(circuits) qml.disable_tape()
def test_serialize_circuit_no_rotations_tape(self, monkeypatch, tmpdir, test_batch_result): """Test that a circuit that is serialized correctly without rotations for a simulator backend in tape mode""" qml.enable_tape() dev = QeQiskitDevice(wires=1, shots=1000, backend="statevector_simulator", analytic=True) circuit_history = [] with qml.tape.QuantumTape() as tape1: qml.Hadamard(wires=[0]) qml.expval(qml.Hadamard(0)) with monkeypatch.context() as m: m.setattr(pennylane_orquestra.cli_actions, "user_data_dir", lambda *args: tmpdir) m.setattr( pennylane_orquestra.orquestra_device, "gen_expval_workflow", lambda component, backend_specs, circuits, operators, **kwargs: circuit_history.extend(circuits), ) # Disable submitting to the Orquestra platform by mocking Popen m.setattr(subprocess, "Popen", lambda *args, **kwargs: MockPopen()) m.setattr( pennylane_orquestra.orquestra_device, "loop_until_finished", lambda *args, **kwargs: test_batch_result, # The exact results are not considered in the test ) dev.execute(tape1) expected = 'OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[1];\ncreg c[1];\nh q[0];\n' assert circuit_history[0] == expected qml.disable_tape()
def test_non_tape_mode(self): """Tests that an exception is raised when attempting to use caching outside of tape mode""" dev = qml.device("default.qubit", wires=3, cache=10) try: qml.disable_tape() def qfunc(x, y): """Simple quantum function""" qml.RX(x, wires=0) qml.RX(y, wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(wires=1)) qn = qml.QNode(qfunc, dev) with pytest.raises( ValueError, match="Caching is only available when using tape mode"): qn(0.1, 0.2) finally: qml.enable_tape()
def enable_tape_mode(self): qml.enable_tape() yield qml.disable_tape()
def _construct(self, args, kwargs): """Construct the quantum circuit graph by calling the quantum function. For immutable nodes this method is called the first time :meth:`BaseQNode.evaluate` or :meth:`.JacobianQNode.jacobian` is called, and for mutable nodes *each time* they are called. It executes the quantum function, stores the resulting sequence of :class:`.Operator` instances, converts it into a circuit graph, and creates the Variable mapping. .. note:: The Variables are only required for analytic differentiation, for evaluation we could simply reconstruct the circuit each time. Args: args (tuple[Any]): Positional arguments passed to the quantum function. During the construction we are not concerned with the numerical values, but with the nesting structure. Each positional argument is replaced with a :class:`~.Variable` instance. kwargs (dict[str, Any]): Auxiliary arguments passed to the quantum function. """ # TODO: Update the docstring to reflect the kwargs and the raising conditions # pylint: disable=attribute-defined-outside-init, too-many-branches, too-many-statements self.arg_vars, self.kwarg_vars = self._make_variables(args, kwargs) # temporary queues for operations and observables # TODO rename self.queue to self.op_queue self.queue = [] #: list[Operation]: applied operations self.obs_queue = [] #: list[Observable]: applied observables tape_mode = qml.tape_mode_active() if tape_mode: qml.disable_tape() try: # set up the context for Operator entry with self: try: # generate the program queue by executing the quantum circuit function if self.mutable: # it's ok to directly pass auxiliary arguments since the circuit is # re-constructed each time (positional args must be replaced because # parameter-shift differentiation requires Variables) res = self.func(*self.arg_vars, **kwargs) else: # TODO: Maybe we should only convert the kwarg_vars that were actually given res = self.func(*self.arg_vars, **self.kwarg_vars) except: # The qfunc call may have failed because the user supplied bad parameters, # which is why we must wipe the created Variables. self.arg_vars = None self.kwarg_vars = None raise finally: if tape_mode: qml.enable_tape() # check the validity of the circuit self._check_circuit(res) del self.queue del self.obs_queue # Prune all the Tensor objects that have been used in the circuit self.ops = self._prune_tensors(self.ops) # map each free variable to the operators which depend on it self.variable_deps = {k: [] for k in range(self.num_variables)} for op in self.ops: for j, p in enumerate(_flatten(op.data)): if isinstance(p, Variable): if not p.is_kwarg: # ignore auxiliary arguments self.variable_deps[p.idx].append(ParameterDependency(op, j)) # generate the DAG self.circuit = CircuitGraph(self.ops, self.variable_deps, self.device.wires) # check for unused positional params if self.kwargs.get("par_check", False): unused = [k for k, v in self.variable_deps.items() if not v] if unused: raise QuantumFunctionError( "The positional parameters {} are unused.".format(unused) ) # check for operations that cannot affect the output if self.kwargs.get("vis_check", False): invisible = self.circuit.invisible_operations() if invisible: raise QuantumFunctionError( "The operations {} cannot affect the circuit output.".format(invisible) )
def __init__( self, ansatz, hamiltonian, device, interface="autograd", diff_method="best", optimize=False, **kwargs, ): coeffs, observables = hamiltonian.terms self.hamiltonian = hamiltonian """Hamiltonian: the input Hamiltonian.""" self.qnodes = None """QNodeCollection: The QNodes to be evaluated. Each QNode corresponds to the expectation value of each observable term after applying the circuit ansatz.""" self._multiple_devices = isinstance(device, Sequence) """Bool: Records if multiple devices are input""" tape_mode = qml.tape_mode_active() if tape_mode: d = device[0] if self._multiple_devices else device w = d.wires.tolist() try: qml.disable_tape() @qml.qnode(d, interface=interface, diff_method=diff_method, **kwargs) def qnode_for_metric_tensor_in_tape_mode( *qnode_args, **qnode_kwargs): """The metric tensor cannot currently be calculated in tape-mode QNodes. As a short-term fix for ExpvalCost, we create a non-tape mode QNode just for calculation of the metric tensor. In doing so, we reintroduce the same restrictions of the old QNode but allow users to access new functionality such as measurement grouping and batch execution of the gradient.""" ansatz(*qnode_args, wires=w, **qnode_kwargs) return qml.expval(qml.PauliZ(0)) self._qnode_for_metric_tensor_in_tape_mode = qnode_for_metric_tensor_in_tape_mode finally: qml.enable_tape() self._optimize = optimize if self._optimize: if not tape_mode: raise ValueError( "Observable optimization is only supported in tape mode. Tape " "mode can be enabled with the command:\n" "qml.enable_tape()") if self._multiple_devices: raise ValueError( "Using multiple devices is not supported when optimize=True" ) obs_groupings, coeffs_groupings = qml.grouping.group_observables( observables, coeffs) @qml.qnode(device, interface=interface, diff_method=diff_method, **kwargs) def circuit(*qnode_args, obs, **qnode_kwargs): """Converting ansatz into a full circuit including measurements""" ansatz(*qnode_args, wires=w, **qnode_kwargs) return [qml.expval(o) for o in obs] def cost_fn(*qnode_args, **qnode_kwargs): """Combine results from grouped QNode executions with grouped coefficients""" total = 0 for o, c in zip(obs_groupings, coeffs_groupings): res = circuit(*qnode_args, obs=o, **qnode_kwargs) total += sum([r * c_ for r, c_ in zip(res, c)]) return total self.cost_fn = cost_fn else: self.qnodes = qml.map(ansatz, observables, device, interface=interface, diff_method=diff_method, **kwargs) self.cost_fn = qml.dot(coeffs, self.qnodes)
def non_tape_mode_only(): """Run the test in tape mode""" qml.disable_tape() yield qml.enable_tape()
def test_batch_exec_multiple_workflow(self, keep, dev_name, tmpdir, monkeypatch, test_batch_result): """Test that the batch_execute method returns the desired result and that the result preserves the order in which circuits were submitted when batches are created in multiple workflows .""" qml.enable_tape() with qml.tape.QuantumTape() as tape1: qml.RX(0.133, wires=0) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(wires=[0])) with qml.tape.QuantumTape() as tape2: qml.RX(0.432, wires=0) qml.RY(0.543, wires=0) qml.expval(qml.PauliZ(wires=[0])) with qml.tape.QuantumTape() as tape3: qml.RX(0.432, wires=0) qml.expval(qml.PauliZ(wires=[0])) circuits = [tape1, tape2, tape3] # Setting batch size: allow only a single circuit for each workflow dev = qml.device(dev_name, wires=3, batch_size=1, keep_files=keep) # Check that no workflow files were created before test_uuid = "1234" assert not os.path.exists(tmpdir.join(f"expval-{test_uuid}-0.yaml")) assert not os.path.exists(tmpdir.join(f"expval-{test_uuid}-1.yaml")) assert not os.path.exists(tmpdir.join(f"expval-{test_uuid}-2.yaml")) with monkeypatch.context() as m: m.setattr(pennylane_orquestra.cli_actions, "user_data_dir", lambda *args: tmpdir) # Disable submitting to the Orquestra platform by mocking Popen m.setattr(subprocess, "Popen", lambda *args, **kwargs: MockPopen()) m.setattr( pennylane_orquestra.orquestra_device, "loop_until_finished", lambda *args, **kwargs: test_batch_result, ) # Disable random uuid generation m.setattr(uuid, "uuid4", lambda *args: test_uuid) res = dev.batch_execute(circuits) # Correct order of results is expected assert np.allclose(res[0], test_batch_res0) assert np.allclose(res[1], test_batch_res1) assert np.allclose(res[2], test_batch_res2) file0_kept = os.path.exists( tmpdir.join(f"expval-{test_uuid}-0.yaml")) file1_kept = os.path.exists( tmpdir.join(f"expval-{test_uuid}-1.yaml")) file2_kept = os.path.exists( tmpdir.join(f"expval-{test_uuid}-2.yaml")) # Check that workflow files were either all kept or all deleted files_kept = file0_kept and file1_kept and file2_kept assert files_kept and file0_kept if keep else not files_kept qml.disable_tape()
def in_tape_mode(): """Run the test in tape mode""" qml.enable_tape() yield qml.disable_tape()