def scatter_element_add(tensor, index, value, like=None): """In-place addition of a multidimensional value over various indices of a tensor. Args: tensor (tensor_like[float]): Tensor to add the value to index (tuple or list[tuple]): Indices to which to add the value value (float or tensor_like[float]): Value to add to ``tensor`` like (str): Manually chosen interface to dispatch to. Returns: tensor_like[float]: The tensor with the value added at the given indices. **Example** >>> tensor = torch.tensor([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]) >>> index = (1, 2) >>> value = -3.1 >>> qml.math.scatter_element_add(tensor, index, value) tensor([[ 0.1000, 0.2000, 0.3000], [ 0.4000, 0.5000, -2.5000]]) If multiple indices are given, in the form of a list of tuples, the ``k`` th tuple is interpreted to contain the ``k`` th entry of all indices: >>> indices = [(1, 0), (2, 1)] # This will modify the entries (1, 2) and (0, 1) >>> values = torch.tensor([10, 20]) >>> qml.math.scatter_element_add(tensor, indices, values) tensor([[ 0.1000, 20.2000, 0.3000], [ 0.4000, 0.5000, 10.6000]]) """ interface = like or _multi_dispatch([tensor, value]) return np.scatter_element_add(tensor, index, value, like=interface)
def cov_matrix(prob, obs, wires=None, diag_approx=False): """Calculate the covariance matrix of a list of commuting observables, given the joint probability distribution of the system in the shared eigenbasis. .. note:: This method only works for **commuting observables.** If the probability distribution is the result of a quantum circuit, the quantum state must be rotated into the shared eigenbasis of the list of observables before measurement. Args: prob (tensor_like): probability distribution obs (list[.Observable]): a list of observables for which to compute the covariance matrix for diag_approx (bool): if True, return the diagonal approximation wires (.Wires): The wire register of the system. If not provided, it is assumed that the wires are labelled with consecutive integers. Returns: tensor_like: the covariance matrix of size ``(len(obs), len(obs))`` **Example** Consider the following ansatz and observable list: >>> obs_list = [qml.PauliX(0) @ qml.PauliZ(1), qml.PauliY(2)] >>> ansatz = qml.templates.StronglyEntanglingLayers We can construct a QNode to output the probability distribution in the shared eigenbasis of the observables: .. code-block:: python dev = qml.device("default.qubit", wires=3) @qml.qnode(dev, interface="autograd") def circuit(weights): ansatz(weights, wires=[0, 1, 2]) # rotate into the basis of the observables for o in obs_list: o.diagonalizing_gates() return qml.probs(wires=[0, 1, 2]) We can now compute the covariance matrix: >>> weights = qml.init.strong_ent_layers_normal(n_layers=2, n_wires=3) >>> cov = qml.math.cov_matrix(circuit(weights), obs_list) >>> cov array([[0.98707611, 0.03665537], [0.03665537, 0.99998377]]) Autodifferentiation is fully supported using all interfaces. Here we use autograd: >>> cost_fn = lambda weights: qml.math.cov_matrix(circuit(weights), obs_list)[0, 1] >>> qml.grad(cost_fn)(weights)[0] array([[[ 4.94240914e-17, -2.33786398e-01, -1.54193959e-01], [-3.05414996e-17, 8.40072236e-04, 5.57884080e-04], [ 3.01859411e-17, 8.60411436e-03, 6.15745204e-04]], [[ 6.80309533e-04, -1.23162742e-03, 1.08729813e-03], [-1.53863193e-01, -1.38700657e-02, -1.36243323e-01], [-1.54665054e-01, -1.89018172e-02, -1.56415558e-01]]]) """ variances = [] # diagonal variances for i, o in enumerate(obs): l = cast(o.eigvals, dtype=float64) w = o.wires.labels if wires is None else wires.indices(o.wires) p = marginal_prob(prob, w) res = dot(l**2, p) - (dot(l, p))**2 variances.append(res) cov = diag(variances) if diag_approx: return cov for i, j in itertools.combinations(range(len(obs)), r=2): o1 = obs[i] o2 = obs[j] o1wires = o1.wires.labels if wires is None else wires.indices(o1.wires) o2wires = o2.wires.labels if wires is None else wires.indices(o2.wires) shared_wires = set(o1wires + o2wires) l1 = cast(o1.eigvals, dtype=float64) l2 = cast(o2.eigvals, dtype=float64) l12 = cast(np.kron(l1, l2), dtype=float64) p1 = marginal_prob(prob, o1wires) p2 = marginal_prob(prob, o2wires) p12 = marginal_prob(prob, shared_wires) res = dot(l12, p12) - dot(l1, p1) * dot(l2, p2) cov = np.scatter_element_add(cov, [i, j], res) cov = np.scatter_element_add(cov, [j, i], res) return cov