def test_with_qnode(self, qnode, params, ids, nums_frequency, spectra, shifts, exp_calls, mocker): """Run a full reconstruction on a QNode.""" qnode = qml.QNode(qnode, dev_1) with qml.Tracker(qnode.device) as tracker: recons = reconstruct(qnode, ids, nums_frequency, spectra, shifts)(*params) assert tracker.totals["executions"] == exp_calls arg_names = list(signature(qnode.func).parameters.keys()) for outer_key in recons: outer_key_num = arg_names.index(outer_key) for inner_key, rec in recons[outer_key].items(): x0 = params[outer_key_num] if not pnp.isscalar(x0): x0 = x0[inner_key] shift_vec = qml.math.zeros_like(params[outer_key_num]) shift_vec[inner_key] = 1.0 shift_vec = 1.0 if pnp.isscalar( params[outer_key_num]) else shift_vec mask = (0.0 if pnp.isscalar(params[outer_key_num]) else pnp.ones(qml.math.shape(params[outer_key_num])) - shift_vec) univariate = lambda x: qnode( *params[:outer_key_num], params[outer_key_num] * mask + x * shift_vec, *params[outer_key_num + 1:], ) assert np.isclose(rec(x0), qnode(*params)) assert np.isclose(rec(x0 + 0.1), univariate(x0 + 0.1)) assert fun_close(rec, univariate, 10)
def test_batch_execute_parallel_tracker(mock_run_batch): """Asserts tracker updates during parallel execution""" mock_run_batch.return_value = TASK_BATCH type(TASK_BATCH).unsuccessful = PropertyMock(return_value={}) dev = _aws_device(wires=1, foo="bar", parallel=True) with QuantumTape() as circuit: qml.Hadamard(wires=0) qml.probs(wires=(0, )) circuits = [circuit, circuit] callback = Mock() with qml.Tracker(dev, callback=callback) as tracker: dev.batch_execute(circuits) dev.batch_execute(circuits) latest = {"batches": 1, "executions": 2, "shots": 2 * SHOTS} history = { "batches": [1], "executions": [2], "shots": [2 * SHOTS], "braket_task_id": ["task_arn", "task_arn"], } totals = {"batches": 1, "executions": 2, "shots": 2 * SHOTS} assert tracker.latest == latest assert tracker.history == history assert tracker.totals == totals callback.assert_called_with(latest=latest, history=history, totals=totals)
def test_fewer_device_invocations_vector_output(self): """Test that the hessian invokes less hardware executions than double differentiation (1d -> 1d)""" dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, diff_method="parameter-shift", max_diff=2) def circuit(x): qml.RX(x[0], wires=0) qml.CNOT(wires=[0, 1]) qml.RY(x[1], wires=0) qml.RZ(x[2], wires=1) return qml.probs(wires=[0, 1]) x = np.array([0.1, 0.2, 0.3], requires_grad=True) with qml.Tracker(dev) as tracker: hessian = qml.gradients.param_shift_hessian(circuit)(x) hessian_qruns = tracker.totals["executions"] expected = qml.jacobian(qml.jacobian(circuit))(x) jacobian_qruns = tracker.totals["executions"] - hessian_qruns assert np.allclose(hessian, expected) assert hessian_qruns < jacobian_qruns assert hessian_qruns <= 2**2 * 6 # 6 = (3+2-1)C(2) assert hessian_qruns <= 3**3
def test_batch_execute_non_parallel_tracker(mock_run): """Tests tracking for a non-parallel batch""" mock_run.return_value = TASK dev = _aws_device(wires=2, foo="bar", parallel=False) with QuantumTape() as circuit: qml.Hadamard(wires=0) qml.probs(wires=(0, )) callback = Mock() with qml.Tracker(dev, callback=callback) as tracker: dev.batch_execute([circuit, circuit]) dev.batch_execute([circuit]) latest = {"batches": 1, "batch_len": 2} history = { "executions": [1, 1], "shots": [SHOTS, SHOTS], "batches": [1], "batch_len": [2], "braket_task_id": ["task_arn", "task_arn"], } totals = { "executions": 2, "shots": 2 * SHOTS, "batches": 1, "batch_len": 2 } assert tracker.latest == latest assert tracker.history == history assert tracker.totals == totals callback.assert_called_with(latest=latest, history=history, totals=totals)
def test_tracking(self, device, shots, tol): """Tests that a Device Tracker example correctly records resource usage""" # This test is run for both local and AWS managed simulators dev = device(1) @qml.qnode(dev, diff_method="parameter-shift") def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) x = np.array(0.1, requires_grad=True) with qml.Tracker(circuit.device) as tracker: qml.grad(circuit)(x) expected_totals = { "executions": 3, "shots": 300, "batches": 2, "batch_len": 3 } expected_history = { "executions": [1, 1, 1], "shots": [100, 100, 100], "batches": [1, 1], "batch_len": [1, 2], } # Breaking change in PL 0.20 affects how many batches are created from the gradient if qml.version() < "0.20": expected_totals["batches"] = 1 expected_totals["batch_len"] = 2 expected_history["batches"] = [1] expected_history["batch_len"] = [2] expected_latest = {"batches": 1, "batch_len": 2} for key, total in expected_totals.items(): assert tracker.totals[key] == total for key, history in expected_history.items(): assert tracker.history[key] == history assert tracker.latest == expected_latest assert len(tracker.history["braket_task_id"]) == 3 if type(dev) == BraketAwsQubitDevice: durations = tracker.history["braket_simulator_ms"] billed_durations = tracker.history["braket_simulator_billed_ms"] assert len(durations) == 3 assert len(billed_durations) == 3 for duration, billed in zip(durations, billed_durations): assert (duration < MIN_SIMULATOR_BILLED_MS and billed == MIN_SIMULATOR_BILLED_MS) or duration == billed
def test_differentiability_torch( self, qnode, params, ids, nums_frequency, spectra, shifts, exp_calls, mocker ): """Tests the reconstruction and differentiability with Torch.""" torch = pytest.importorskip("torch") qnode = qml.QNode(qnode, dev_1, interface="torch") params = tuple(torch.tensor(par, requires_grad=True, dtype=torch.float64) for par in params) if spectra is not None: spectra = { outer_key: { inner_key: torch.tensor(val, dtype=torch.float64) for inner_key, val in outer_val.items() } for outer_key, outer_val in spectra.items() } if shifts is not None: shifts = { outer_key: { inner_key: torch.tensor(val, dtype=torch.float64) for inner_key, val in outer_val.items() } for outer_key, outer_val in shifts.items() } with qml.Tracker(qnode.device) as tracker: recons = reconstruct(qnode, ids, nums_frequency, spectra, shifts)(*params) assert tracker.totals["executions"] == exp_calls arg_names = list(signature(qnode.func).parameters.keys()) for outer_key in recons: outer_key_num = arg_names.index(outer_key) for inner_key, rec in recons[outer_key].items(): x0 = params[outer_key_num] if not len(qml.math.shape(x0)) == 0: x0 = x0[inner_key] shift_vec = qml.math.zeros_like(params[outer_key_num]) shift_vec = qml.math.scatter_element_add(shift_vec, inner_key, 1.0) mask = torch.ones(qml.math.shape(params[outer_key_num])) - shift_vec else: shift_vec = 1.0 mask = 0.0 univariate = lambda x: qnode( *params[:outer_key_num], params[outer_key_num] * mask + x * shift_vec, *params[outer_key_num + 1 :], ) exp_qnode_grad = torch.autograd.functional.jacobian(qnode, params)[outer_key_num] exp_grad = lambda x: torch.autograd.functional.jacobian(univariate, x) grad = lambda x: torch.autograd.functional.jacobian(rec, x) assert np.isclose(grad(x0), exp_qnode_grad[inner_key]) assert np.isclose(grad(x0 + 0.1), exp_grad(x0 + 0.1)) assert fun_close( grad, exp_grad, zero=torch.tensor(0.0, requires_grad=True), samples=10 )
def test_batch_execute_partial_fail_parallel_tracker(mock_run_batch): """Asserts tracker updates during a partial failure of parallel execution""" FAIL_TASK = Mock() FAIL_TASK.result.return_value = None type(FAIL_TASK).id = PropertyMock(return_value="failed_task_arn") FAIL_TASK.state.return_value = "FAILED" FAIL_BATCH = Mock() FAIL_BATCH.results.side_effect = RuntimeError("tasks failed to complete") type(FAIL_BATCH).tasks = PropertyMock(return_value=[SIM_TASK, FAIL_TASK]) type(FAIL_BATCH).unsuccessful = PropertyMock( return_value={"failed_task_arn"}) mock_run_batch.return_value = FAIL_BATCH dev = _aws_device(wires=1, foo="bar", parallel=True) with QuantumTape() as circuit: qml.Hadamard(wires=0) qml.probs(wires=(0, )) circuits = [circuit, circuit] callback = Mock() try: with qml.Tracker(dev, callback=callback) as tracker: dev.batch_execute(circuits) dev.batch_execute(circuits) except RuntimeError: pass latest = {"batches": 1, "executions": 1, "shots": 1 * SHOTS} history = { "batches": [1], "executions": [1], "shots": [1 * SHOTS], "braket_task_id": ["task_arn"], "braket_failed_task_id": ["failed_task_arn"], "braket_simulator_ms": [1234], "braket_simulator_billed_ms": [3000], } totals = { "batches": 1, "executions": 1, "shots": 1 * SHOTS, "braket_simulator_ms": 1234, "braket_simulator_billed_ms": 3000, } assert tracker.latest == latest assert tracker.history == history assert tracker.totals == totals callback.assert_called_with(latest=latest, history=history, totals=totals)
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_execute_tracker(mock_run): """Asserts tracker stores information during execute when active""" mock_run.side_effect = [TASK, SIM_TASK, SIM_TASK, TASK] dev = _aws_device(wires=4, foo="bar") with QuantumTape() as circuit: qml.Hadamard(wires=0) qml.probs(wires=(0, )) callback = Mock() with qml.Tracker(dev, callback=callback) as tracker: dev.execute(circuit) dev.execute(circuit) dev.execute(circuit) dev.execute(circuit) latest = { "executions": 1, "shots": SHOTS, "braket_task_id": "task_arn", "braket_simulator_ms": 1234, "braket_simulator_billed_ms": 3000, } history = { "executions": [1, 1, 1], "shots": [SHOTS, SHOTS, SHOTS], "braket_task_id": ["task_arn", "task_arn", "task_arn"], "braket_simulator_ms": [1234, 1234], "braket_simulator_billed_ms": [3000, 3000], } totals = { "executions": 3, "shots": 3 * SHOTS, "braket_simulator_ms": 2468, "braket_simulator_billed_ms": 6000, } assert tracker.latest == latest assert tracker.history == history assert tracker.totals == totals callback.assert_called_with(latest=latest, history=history, totals=totals)
def test_differentiability_jax(self, qnode, params, ids, nums_frequency, spectra, shifts, exp_calls, mocker): """Tests the reconstruction and differentiability with JAX.""" jax = pytest.importorskip("jax") from jax.config import config config.update("jax_enable_x64", True) params = tuple(jax.numpy.array(par) for par in params) qnode = qml.QNode(qnode, dev_1, interface="jax") with qml.Tracker(qnode.device) as tracker: recons = reconstruct(qnode, ids, nums_frequency, spectra, shifts)(*params) assert tracker.totals["executions"] == exp_calls arg_names = list(signature(qnode.func).parameters.keys()) for outer_key in recons: outer_key_num = arg_names.index(outer_key) for inner_key, rec in recons[outer_key].items(): x0 = params[outer_key_num] if not pnp.isscalar(x0): x0 = x0[inner_key] shift_vec = qml.math.zeros_like(params[outer_key_num]) shift_vec = qml.math.scatter_element_add( shift_vec, inner_key, 1.0) shift_vec = 1.0 if pnp.isscalar( params[outer_key_num]) else shift_vec mask = (0.0 if pnp.isscalar(params[outer_key_num]) else pnp.ones(qml.math.shape(params[outer_key_num])) - shift_vec) univariate = lambda x: qnode( *params[:outer_key_num], params[outer_key_num] * mask + x * shift_vec, *params[outer_key_num + 1:], ) exp_qnode_grad = jax.grad(qnode, argnums=outer_key_num) exp_grad = jax.grad(univariate) grad = jax.grad(rec) assert np.isclose(grad(x0), exp_qnode_grad(*params)[inner_key]) assert np.isclose(grad(x0 + 0.1), exp_grad(x0 + 0.1)) assert fun_close(grad, exp_grad, 10)
def __init__(self, wires=1, shots=1000, *, analytic=None): self.shots = shots if analytic is not None: msg = "The analytic argument has been replaced by shots=None. " msg += "Please use shots=None instead of analytic=True." raise DeviceError(msg) if not isinstance(wires, Iterable): # interpret wires as the number of consecutive wires wires = range(wires) self._wires = Wires(wires) self.num_wires = len(self._wires) self._wire_map = self.define_wire_map(self._wires) self._num_executions = 0 self._op_queue = None self._obs_queue = None self._parameters = None self.tracker = qml.Tracker()
def test_differentiability_autograd(self, qnode, params, ids, nums_frequency, spectra, shifts, exp_calls, mocker): """Tests the reconstruction and differentiability with autograd.""" qnode = qml.QNode(qnode, dev_1, interface="autograd") with qml.Tracker(qnode.device) as tracker: recons = reconstruct(qnode, ids, nums_frequency, spectra, shifts)(*params) assert tracker.totals["executions"] == exp_calls arg_names = list(signature(qnode.func).parameters.keys()) for outer_key in recons: outer_key_num = arg_names.index(outer_key) for inner_key, rec in recons[outer_key].items(): x0 = params[outer_key_num] if not pnp.isscalar(x0): x0 = x0[inner_key] shift_vec = qml.math.zeros_like(params[outer_key_num]) shift_vec[inner_key] = 1.0 shift_vec = 1.0 if pnp.isscalar( params[outer_key_num]) else shift_vec mask = (0.0 if pnp.isscalar(params[outer_key_num]) else pnp.ones(qml.math.shape(params[outer_key_num])) - shift_vec) univariate = lambda x: qnode( *params[:outer_key_num], params[outer_key_num] * mask + x * shift_vec, *params[outer_key_num + 1:], ) exp_qnode_grad = qml.grad(qnode, argnum=outer_key_num) exp_grad = qml.grad(univariate) grad = qml.grad(rec) if nums_frequency is None: # Gradient evaluation at reconstruction point not supported for # Dirichlet reconstruction assert np.isclose(grad(x0), exp_qnode_grad(*params)[inner_key]) assert np.isclose(grad(x0 + 0.1), exp_grad(x0 + 0.1)) assert fun_close(grad, exp_grad, 10)
def test_f0_argument(self): """Test that we can provide the results of a QNode to save on quantum invocations""" dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, diff_method="parameter-shift", max_diff=2) def circuit(x): qml.RX(x[0], wires=0) qml.RY(x[1], wires=0) qml.CNOT(wires=[0, 1]) return qml.probs(wires=1) x = np.array([0.1, 0.2], requires_grad=True) res = circuit(x) with qml.Tracker(dev) as tracker: hessian1 = qml.gradients.param_shift_hessian(circuit, f0=res)(x) qruns1 = tracker.totals["executions"] hessian2 = qml.gradients.param_shift_hessian(circuit)(x) qruns2 = tracker.totals["executions"] - qruns1 assert np.allclose(hessian1, hessian2) assert qruns1 < qruns2
def test_fewer_device_invocations_scalar_input(self): """Test that the hessian invokes less hardware executions than double differentiation (0d -> 0d)""" dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, diff_method="parameter-shift", max_diff=2) def circuit(x): qml.RX(x, wires=0) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(1)) x = np.array(0.1, requires_grad=True) with qml.Tracker(dev) as tracker: hessian = qml.gradients.param_shift_hessian(circuit)(x) hessian_qruns = tracker.totals["executions"] expected = qml.jacobian(qml.jacobian(circuit))(x) jacobian_qruns = tracker.totals["executions"] - hessian_qruns assert np.allclose(hessian, expected) assert hessian_qruns < jacobian_qruns assert hessian_qruns <= 2**2 * 1 # 1 = (1+2-1)C(2) assert hessian_qruns <= 3**1
def test_differentiability_tensorflow(self, qnode, params, ids, nums_frequency, spectra, shifts, exp_calls, mocker): """Tests the reconstruction and differentiability with TensorFlow.""" if qnode == qnode_4: pytest.skip( "Gradients are empty in TensorFlow for independent functions.") tf = pytest.importorskip("tensorflow") qnode = qml.QNode(qnode, dev_1, interface="tf") params = tuple(tf.Variable(par, dtype=tf.float64) for par in params) if spectra is not None: spectra = { outer_key: { inner_key: tf.constant(val, dtype=tf.float64) for inner_key, val in outer_val.items() } for outer_key, outer_val in spectra.items() } if shifts is not None: shifts = { outer_key: { inner_key: tf.constant(val, dtype=tf.float64) for inner_key, val in outer_val.items() } for outer_key, outer_val in shifts.items() } with qml.Tracker(qnode.device) as tracker: recons = reconstruct(qnode, ids, nums_frequency, spectra, shifts)(*params) assert tracker.totals["executions"] == exp_calls arg_names = list(signature(qnode.func).parameters.keys()) for outer_key in recons: outer_key_num = arg_names.index(outer_key) for inner_key, rec in recons[outer_key].items(): if outer_key == "Z" and inner_key == (1, 3): # This is a constant function dependence, which can # not be properly resolved by this test. continue x0 = params[outer_key_num] if not len(qml.math.shape(x0)) == 0: x0 = x0[inner_key] shift_vec = qml.math.zeros_like(params[outer_key_num]) shift_vec = qml.math.scatter_element_add( shift_vec, inner_key, 1.0) mask = pnp.ones(qml.math.shape( params[outer_key_num])) - shift_vec else: shift_vec = 1.0 mask = 0.0 univariate = lambda x: qnode( *params[:outer_key_num], params[outer_key_num] * mask + x * shift_vec, *params[outer_key_num + 1:], ) with tf.GradientTape() as tape: out = qnode(*params) exp_qnode_grad = tape.gradient(out, params[outer_key_num]) def exp_grad(x): x = tf.Variable(x, dtype=tf.float64) with tf.GradientTape() as tape: out = univariate(x) return tape.gradient(out, x) def grad(x): x = tf.Variable(x, dtype=tf.float64) with tf.GradientTape() as tape: out = rec(x) return tape.gradient(out, x) if nums_frequency is None: # Gradient evaluation at reconstruction point not supported for # Dirichlet reconstruction assert np.isclose(grad(x0), exp_qnode_grad[inner_key]) assert np.isclose(grad(x0 + 0.1), exp_grad(x0 + 0.1)) assert fun_close(grad, exp_grad, 10)