def test_dot_product_qnodes_tensor(self, qnodes, interface, tf_support, torch_support): """Test that the dot product of qnodes.tensor can be applied using all interfaces""" if interface == "torch" and not torch_support: pytest.skip("Skipped, no torch support") if interface == "tf" and not tf_support: pytest.skip("Skipped, no tf support") qnode1, _ = qnodes qc = qml.QNodeCollection([qnode1]) coeffs = [0.5, -0.1] if interface == "torch": coeffs = torch.tensor(coeffs, dtype=torch.float64) if interface == "tf": coeffs = tf.cast(coeffs, dtype=tf.float64) # test the dot product of qnodes, tensor cost = qml.dot(qc, coeffs) params = [0.5643, -0.45] res = cost(params) qcval = qc(params) if interface in ("tf", "torch"): res = res.numpy() qcval = qcval.numpy() coeffs = coeffs.numpy() expected = np.dot(qcval, coeffs) assert np.all(res == expected)
def test_dot_product_qnodes_qnodes(self, qnodes, interface, tf_support, torch_support): """Test that the dot product of qnodes.qnodes can be applied using all interfaces""" if interface == "torch" and not torch_support: pytest.skip("Skipped, no torch support") if interface == "tf" and not tf_support: pytest.skip("Skipped, no tf support") qnode1, qnode2 = qnodes qc1 = qml.QNodeCollection([qnode1, qnode2]) qc2 = qml.QNodeCollection([qnode1, qnode2]) # test the dot product of qnodes, qnodes cost = qml.dot(qc1, qc2) params = [0.5643, -0.45] res = cost(params) qc1val = qc1(params) qc2val = qc2(params) if interface in ("tf", "torch"): res = res.numpy() qc1val = qc1val.numpy() qc2val = qc2val.numpy() expected = np.dot(qc1val, qc2val) assert np.all(res == expected)
def test_mismatching_interface(self, monkeypatch): """Test exception raised if the interfaces don't match""" dev = qml.device("default.qubit", wires=1) @qml.qnode(dev, interface=None) def circuit1(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) @qml.qnode(dev, interface="autograd") def circuit2(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) qc1 = qml.QNodeCollection([circuit1]) qc2 = qml.QNodeCollection([circuit2]) with pytest.raises(ValueError, match="have non-matching interfaces"): qml.dot(qc1, qc2)
def test_unknown_interface(self, monkeypatch): """Test exception raised if the interface is unknown""" monkeypatch.setattr(qml.QNodeCollection, "interface", "invalid") dev = qml.device("default.qubit", wires=1) @qml.qnode(dev) def circuit1(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) @qml.qnode(dev) def circuit2(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) qc = qml.QNodeCollection([circuit1, circuit2]) with pytest.raises(ValueError, match="Unknown interface invalid"): qml.dot([1, 2], qc)
def __init__( self, ansatz, hamiltonian, device, interface="autograd", diff_method="best", optimize=False, **kwargs, ): coeffs, observables = hamiltonian.terms self.hamiltonian = hamiltonian """Hamiltonian: the hamiltonian defining the VQE problem.""" 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._optimize = optimize if self._optimize: if not qml.tape_mode_active(): raise ValueError( "Observable optimization is only supported in tape mode. Tape " "mode can be enabled with the command:\n" "qml.enable_tape()" ) obs_groupings, coeffs_groupings = qml.grouping.group_observables(observables, coeffs) wires = device.wires.tolist() @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=wires, **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 __init__(self, ansatz, hamiltonian, device, interface="autograd", diff_method="best"): coeffs, observables = hamiltonian.terms self.hamiltonian = hamiltonian """Hamiltonian: the hamiltonian defining the VQE problem.""" self.qnodes = qml.map(ansatz, observables, device, interface=interface, diff_method=diff_method) """QNodeCollection: The QNodes to be evaluated. Each QNode corresponds to the the expectation value of each observable term after applying the circuit ansatz. """ self.cost_fn = qml.dot(coeffs, self.qnodes)
def test_single_qubit_vqe(self, tol): """Test single-qubit VQE has the correct QNG value every step, the correct parameter updates, and correct cost after 200 steps""" dev = qml.device("default.qubit", wires=1) def circuit(params, wires=0): qml.RX(params[0], wires=wires) qml.RY(params[1], wires=wires) coeffs = [1, 1] obs_list = [ qml.PauliX(0), qml.PauliZ(0) ] qnodes = qml.map(circuit, obs_list, dev, measure='expval') cost_fn = qml.dot(coeffs, qnodes) def gradient(params): """Returns the gradient""" da = -np.sin(params[0]) * (np.cos(params[1]) + np.sin(params[1])) db = np.cos(params[0]) * (np.cos(params[1]) - np.sin(params[1])) return np.array([da, db]) eta = 0.01 init_params = np.array([0.011, 0.012]) num_steps = 200 opt = qml.QNGOptimizer(eta) theta = init_params # optimization for 200 steps total for t in range(num_steps): theta_new = opt.step(cost_fn, theta, metric_tensor_fn=qnodes.qnodes[0].metric_tensor) # check metric tensor res = opt.metric_tensor exp = np.diag([0.25, (np.cos(theta[0]) ** 2)/4]) assert np.allclose(res, exp, atol=tol, rtol=0) # check parameter update dtheta = eta * sp.linalg.pinvh(exp) @ gradient(theta) assert np.allclose(dtheta, theta - theta_new, atol=tol, rtol=0) theta = theta_new # check final cost assert np.allclose(cost_fn(theta), -1.41421356, atol=tol, rtol=0)
def term(params, sign_term, sign_i, i, sign_j, j): qml.BasisState(np.array([0, 0, 0, 0, 0, 0]), wires=range(6)) variational_circuit_wire_list(params) shift = np.zeros_like(params) shift[i] = sign_i * one shift[j] = sign_j * one variational_circuit_wire_list(params + shift, wire_list=[3, 4, 5]) # return qml.DiagonalQubitUnitary([1] * 6, wires=[0, 1, 2]), qml.DiagonalQubitUnitary([1] * 6, wires=[3, 4, 5]) # return [ # [qml.expval(qml.PauliX(wire)) for wire in [0, 1, 2]], # [qml.expval(qml.PauliX(wire)) for wire in [3, 4, 5]], # ] return sign_term * np.abs( qml.dot( [qml.expval(qml.PauliX(wire)) for wire in [0, 1, 2]], [qml.expval(qml.PauliX(wire)) for wire in [3, 4, 5]], ))**2
def test_no_qnodes(self): """Test exception raised if no qnodes are provided as arguments""" with pytest.raises(ValueError, match="At least one argument must be a QNodeCollection"): qml.dot([1, 2], [3, 4])
def test_aggregate_expval(self, coeffs, observables, expected): """Tests that the aggregate function returns correct expectation values""" dev = qml.device("default.qubit", wires=2) qnodes = qml.map(lambda params, **kwargs: None, observables, dev) expval = qml.dot(coeffs, qnodes) assert expval([]) == sum(expected)
def __init__( self, ansatz, hamiltonian, device, interface="autograd", diff_method="best", optimize=False, **kwargs, ): if kwargs.get("measure", "expval") != "expval": raise ValueError("ExpvalCost can only be used to construct sums of expectation values.") 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""" if all(c == 0 for c in coeffs) or not coeffs: self.cost_fn = lambda *args, **kwargs: np.array(0) return self._optimize = optimize self.qnodes = qml.map( ansatz, observables, device, interface=interface, diff_method=diff_method, **kwargs ) if self._optimize: 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) d = device[0] if self._multiple_devices else device w = d.wires.tolist() @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.cost_fn = qml.dot(coeffs, self.qnodes)
for i in range(100): params = opt.step(cost, params) cost_wrs.append(cost(params)) shots_wrs.append(total_shots * i) print("Step {}: cost = {} shots used = {}".format(i, cost_wrs[-1], shots_wrs[-1])) ############################################################################## # Let's compare this against an optimization not using weighted random sampling. # Here, we will split the 8000 total shots evenly across all Hamiltonian terms, # also known as *uniform deterministic sampling*. non_analytic_dev.shots = total_shots / len(coeffs) qnodes = qml.map(StronglyEntanglingLayers, obs, device=non_analytic_dev) cost = qml.dot(coeffs, qnodes) opt = qml.AdamOptimizer(0.05) params = init_params cost_adam = [] shots_adam = [] for i in range(100): params = opt.step(cost, params) cost_adam.append(cost(params)) shots_adam.append(total_shots * i) print("Step {}: cost = {} shots used = {}".format(i, cost_adam[-1], shots_adam[-1])) ##############################################################################
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)
for i in range(100): params, _cost = opt.step_and_cost(cost, params) cost_wrs.append(_cost) shots_wrs.append(total_shots*i) print("Step {}: cost = {} shots used = {}".format(i, cost_wrs[-1], shots_wrs[-1])) ############################################################################## # Let's compare this against an optimization not using weighted random sampling. # Here, we will split the 8000 total shots evenly across all Hamiltonian terms, # also known as *uniform deterministic sampling*. non_analytic_dev.shots = int(total_shots / len(coeffs)) qnodes = qml.map(StronglyEntanglingLayers, obs, device=non_analytic_dev) cost = qml.dot(coeffs, qnodes) opt = qml.AdamOptimizer(0.05) params = init_params cost_adam = [] shots_adam = [] for i in range(100): params, _cost = opt.step_and_cost(cost, params) cost_adam.append(_cost) shots_adam.append(total_shots*i) print("Step {}: cost = {} shots used = {}".format(i, cost_adam[-1], shots_adam[-1])) ############################################################################## # Comparing these two techniques:
def ExpvalH(H: qml.Hamiltonian, device: qml.device): coeffs, observables = H.terms qnodes = qml.map(variational_ansatz, observables, device) cost = qml.dot(coeffs, qnodes) return cost