How do I get the expectation value of multiple wires?

Hello, let’s say that I have a circuit with N wires. I want the output of my function of my QNode to be the expected value of the PauliY operator across all N qubits. How do I go about this? What I have now is

expectation_values = [qml.expval(qml.PauliY(wires=i)) for i in range(nqubits)]
return expectation_values

but this just returns a list of the expected value of each wire. I know that after I execute this circuit I can just do np.mean() on the list, but I want the function to return the overall expected value to use it in an optimization procedue. Thank you for the help!

Hey @NickGut0711!

You’re nearly there! You can do something like this:

import pennylane as qml
import pennylane.numpy as np

dev = qml.device("default.qubit")

@qml.qnode(dev)
def circuit(params):
    for i in range(3):
        qml.RX(params[i], i)
        qml.Hadamard(i)
    
    return [qml.expval(qml.PauliY(i)) for i in range(3)]

def cost(params):
    return np.mean(circuit(params))

params = np.random.uniform(0, np.pi, size=(3,))
cost(params)
0.6752343639895312

Let me know if this helps :slight_smile:

For more information on returning things from circuits in PennyLane, you can check out this awesome documentation page: QNode returns — PennyLane 0.33.0 documentation

Thank you for all the help and the thoughtful response!

When I naively put the similar cost function you described above into my opt.step I get an error. Here’s my code:

def cost(params, phi):
    return np.mean(circuit(params, phi))

L = 1
init_params = create_params(L)
phi = phi = np.array([0.001], requires_grad=False)
nqubits = 10

eta = 0.01
steps = 200

opt = qml.QNGOptimizer(eta)

qng_cost = []

for _ in range(steps):
    print(f'Step: {_}')
    new_params = opt.step(cost, init_params, phi)
    qng_cost.append(circuit(new_params))

Here’s the error:

{
	"name": "ValueError",
	"message": "The objective function must either be encoded as a single QNode or an ExpvalCost object for the natural gradient to be automatically computed. Otherwise, metric_tensor_fn must be explicitly provided to the optimizer.",
	"stack": "---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
 for _ in range(steps):
      print(f'Step: {_}')
--->  new_params = opt.step(cost, init_params, phi)
      qng_cost.append(circuit(new_params))

File ~/..., in QNGOptimizer.step(self, qnode, grad_fn, recompute_tensor, metric_tensor_fn, *args, **kwargs)
    229 def step(
    230     self, qnode, *args, grad_fn=None, recompute_tensor=True, metric_tensor_fn=None, **kwargs
    231 ):
    232     \"\"\"Update the parameter array :math:`x` with one step of the optimizer.
    233 
    234     Args:
   (...)
    251         array: the new variable values :math:`x^{(t+1)}`
    252     \"\"\"
--> 253     new_args, _ = self.step_and_cost(
    254         qnode,
    255         *args,
    256         grad_fn=grad_fn,
    257         recompute_tensor=recompute_tensor,
    258         metric_tensor_fn=metric_tensor_fn,
    259         **kwargs,
    260     )
    261     return new_args

File ~..., in QNGOptimizer.step_and_cost(self, qnode, grad_fn, recompute_tensor, metric_tensor_fn, *args, **kwargs)
    180 # pylint: disable=arguments-differ
    181 if not isinstance(qnode, (qml.QNode, qml.ExpvalCost)) and metric_tensor_fn is None:
--> 182     raise ValueError(
    183         \"The objective function must either be encoded as a single QNode or \"
    184         \"an ExpvalCost object for the natural gradient to be automatically computed. \"
    185         \"Otherwise, metric_tensor_fn must be explicitly provided to the optimizer.\"
    186     )
    188 if recompute_tensor or self.metric_tensor is None:
    189     if metric_tensor_fn is None:

ValueError: The objective function must either be encoded as a single QNode or an ExpvalCost object for the natural gradient to be automatically computed. Otherwise, metric_tensor_fn must be explicitly provided to the optimizer."
}

Now, when I read the documentation here for the QNGOptimizer, I updated my opt.step() to include the metric_tensor_fn argument with the cost function:

for _ in range(steps):
    print(f'Step: {_}')
    new_params = opt.step(circuit, init_params, phi, metric_tensor_fn=cost)
    qng_cost.append(circuit(new_params))

but now I get this error:

{
	"name": "TypeError",
	"message": "'numpy.float64' object cannot be interpreted as an integer",
	"stack": "---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File ~..., in _wrapfunc(obj, method, *args, **kwds)
     58 try:
---> 59     return bound(*args, **kwds)
     60 except TypeError:
     61     # A TypeError occurs if the object does have such a method in its
     62     # class, but its signature is not identical to that of NumPy's. This
   (...)
     66     # Call _wrapit from within the except clause to ensure a potential
     67     # exception has a traceback chain.

TypeError: 'numpy.float64' object cannot be interpreted as an integer

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
/... Cell 13 line 1
for _ in range(steps):
           print(f'Step: {_}')
--->       new_params = opt.step(circuit, init_params, phi, metric_tensor_fn=cost)
           qng_cost.append(circuit(new_params))

File ~/..., in QNGOptimizer.step(self, qnode, grad_fn, recompute_tensor, metric_tensor_fn, *args, **kwargs)
    229 def step(
    230     self, qnode, *args, grad_fn=None, recompute_tensor=True, metric_tensor_fn=None, **kwargs
    231 ):
    232     \"\"\"Update the parameter array :math:`x` with one step of the optimizer.
    233 
    234     Args:
   (...)
    251         array: the new variable values :math:`x^{(t+1)}`
    252     \"\"\"
--> 253     new_args, _ = self.step_and_cost(
    254         qnode,
    255         *args,
    256         grad_fn=grad_fn,
    257         recompute_tensor=recompute_tensor,
    258         metric_tensor_fn=metric_tensor_fn,
    259         **kwargs,
    260     )
    261     return new_args

File ~/..., in QNGOptimizer.step_and_cost(self, qnode, grad_fn, recompute_tensor, metric_tensor_fn, *args, **kwargs)
    194 shape = qml.math.shape(_metric_tensor)
    195 size = qml.math.prod(shape[: len(shape) // 2])
--> 196 self.metric_tensor = qml.math.reshape(_metric_tensor, (size, size))
    197 # Add regularization
    198 self.metric_tensor = self.metric_tensor + self.lam * qml.math.eye(
    199     size, like=_metric_tensor
    200 )

File ~/..., in do(fn, like, *args, **kwargs)
     31 \"\"\"Do function named ``fn`` on ``(*args, **kwargs)``, peforming single
     32 dispatch to retrieve ``fn`` based on whichever library defines the class of
     33 the ``args[0]``, or the ``like`` keyword argument if specified.
   (...)
     77     <tf.Tensor: id=91, shape=(3, 3), dtype=float32>
     78 \"\"\"
     79 backend = choose_backend(fn, *args, like=like, **kwargs)
---> 80 return get_lib_fn(backend, fn)(*args, **kwargs)

File ~/..., in reshape(a, newshape, order)
    200 @array_function_dispatch(_reshape_dispatcher)
    201 def reshape(a, newshape, order='C'):
    202     \"\"\"
    203     Gives a new shape to an array without changing its data.
    204 
   (...)
    283            [5, 6]])
    284     \"\"\"
--> 285     return _wrapfunc(a, 'reshape', newshape, order=order)

File ~/..., in _wrapfunc(obj, method, *args, **kwds)
     59     return bound(*args, **kwds)
     60 except TypeError:
     61     # A TypeError occurs if the object does have such a method in its
     62     # class, but its signature is not identical to that of NumPy's. This
   (...)
     66     # Call _wrapit from within the except clause to ensure a potential
     67     # exception has a traceback chain.
---> 68     return _wrapit(obj, method, *args, **kwds)

File ~/..., in _wrapit(obj, method, *args, **kwds)
     43 except AttributeError:
     44     wrap = None
---> 45 result = getattr(asarray(obj), method)(*args, **kwds)
     46 if wrap:
     47     if not isinstance(result, mu.ndarray):

TypeError: 'numpy.float64' object cannot be interpreted as an integer"
}

I’m unsure what’s going on here. Also, I want to point out that the phi parameter of my circuit is just a parameter with requires_grad=False. Thank you!

Hey @NickGut0711, can you attach your full code? That way I can run it and try to replicate the error you’re getting and propose a solution :smile: