Expected behavior
qml.expval(qml.Projector(state, wires=...)) should return the scalar probability of projecting
the current quantum state onto the specified target state.
The Projector documentation states:
The expectation of this observable returns the value
:math:|\langle\psi|\phi\rangle|^2, corresponding to the probability that
:math:|\psi\rangle is projected onto :math:|\phi\rangle.
For the Bell state
|psi> = (|00> + |11>) / sqrt(2)
the expectation value of Projector([0, 0], wires=[0, 1]) should therefore be the scalar
probability 0.5.
The tableau option of default.clifford should not change this measurement's semantics. Its
documentation says that tableau only determines what is returned when the state is requested
with qml.state():
tableau=True: qml.state() returns the evolved Clifford tableau.
tableau=False: qml.state() returns the evolved state vector.
Therefore, qml.expval(qml.Projector(...)) should return the same scalar expectation value under
both settings.
Actual behavior
With tableau=True, default.clifford correctly returns the scalar 0.5.
With tableau=False, the same Projector expectation returns the complete computational-basis
probability vector:
[0.49999998 0. 0. 0.49999998]
The measurement result consequently has shape (4,), even though an expectation measurement
declares scalar shape ().
default.qubit:
result: 0.4999999999999999
shape: ()
default.clifford(tableau=True):
result: 0.5
shape: ()
default.clifford(tableau=False):
result: [0.49999998 0. 0. 0.49999998]
shape: (4,)
Additional information
The issue appears to be in the analytical BasisStateProjector expectation path in
DefaultClifford._measure_expectation.
The method correctly identifies that the expectation value of a basis-state Projector can be
computed as the probability of its target basis state:
if isinstance(meas_obs, BasisStateProjector):
kwargs["prob_states"] = math.array([meas_obs.data[0]])
return self._measure_probability(
ProbabilityMP(wires=meas_obs.wires), tableau_simulator, **kwargs
).squeeze()
However, when tableau=False, the state-vector branch of _measure_probability returns the full
probability distribution rather than selecting the requested prob_states. The resulting
probability vector is then returned unchanged by _measure_expectation.
This does not appear to be intended tableau=False behavior:
- The
tableau docstring only describes changing the output representation of qml.state().
- Other expectation values, such as
qml.expval(qml.Z(0)), remain scalar with tableau=False.
- The
default.clifford documentation explicitly recommends
qml.expval(qml.Projector(...)) for obtaining the probability of a single target basis state.
ExpectationMP.shape() returns ().
Source code
import numpy as np
import pennylane as qml
def run(device_name, tableau=None):
kwargs = {} if tableau is None else {"tableau": tableau}
dev = qml.device(device_name, wires=2, **kwargs)
@qml.qnode(dev)
def circuit():
# Prepare (|00> + |11>) / sqrt(2)
qml.Hadamard(0)
qml.CNOT(wires=[0, 1])
# The probability of projecting the Bell state onto |00> is 0.5.
return qml.expval(qml.Projector([0, 0], wires=[0, 1]))
result = circuit()
return result, np.shape(result)
for name, tableau in [
("default.qubit", None),
("default.clifford", True),
("default.clifford", False),
]:
result, shape = run(name, tableau)
label = name if tableau is None else f"{name}(tableau={tableau})"
print(f"{label}:")
print(" result:", result)
print(" shape: ", shape)
Tracebacks
default.qubit:
result: 0.4999999999999999
shape: ()
default.clifford(tableau=True):
result: 0.5
shape: ()
default.clifford(tableau=False):
result: [0.49999998 0. 0. 0.49999998]
shape: (4,)
System information
Version: 0.45.0
Platform info: macOS
Python version: 3.13.13
Existing GitHub issues
Expected behavior
qml.expval(qml.Projector(state, wires=...))should return the scalar probability of projectingthe current quantum state onto the specified target state.
The
Projectordocumentation states:For the Bell state
the expectation value of
Projector([0, 0], wires=[0, 1])should therefore be the scalarprobability
0.5.The
tableauoption ofdefault.cliffordshould not change this measurement's semantics. Itsdocumentation says that
tableauonly determines what is returned when the state is requestedwith
qml.state():tableau=True:qml.state()returns the evolved Clifford tableau.tableau=False:qml.state()returns the evolved state vector.Therefore,
qml.expval(qml.Projector(...))should return the same scalar expectation value underboth settings.
Actual behavior
With
tableau=True,default.cliffordcorrectly returns the scalar0.5.With
tableau=False, the same Projector expectation returns the complete computational-basisprobability vector:
The measurement result consequently has shape
(4,), even though an expectation measurementdeclares scalar shape
().Additional information
The issue appears to be in the analytical BasisStateProjector expectation path in
DefaultClifford._measure_expectation.The method correctly identifies that the expectation value of a basis-state Projector can be
computed as the probability of its target basis state:
However, when
tableau=False, the state-vector branch of_measure_probabilityreturns the fullprobability distribution rather than selecting the requested
prob_states. The resultingprobability vector is then returned unchanged by
_measure_expectation.This does not appear to be intended
tableau=Falsebehavior:tableaudocstring only describes changing the output representation ofqml.state().qml.expval(qml.Z(0)), remain scalar withtableau=False.default.clifforddocumentation explicitly recommendsqml.expval(qml.Projector(...))for obtaining the probability of a single target basis state.ExpectationMP.shape()returns().Source code
import numpy as np import pennylane as qml def run(device_name, tableau=None): kwargs = {} if tableau is None else {"tableau": tableau} dev = qml.device(device_name, wires=2, **kwargs) @qml.qnode(dev) def circuit(): # Prepare (|00> + |11>) / sqrt(2) qml.Hadamard(0) qml.CNOT(wires=[0, 1]) # The probability of projecting the Bell state onto |00> is 0.5. return qml.expval(qml.Projector([0, 0], wires=[0, 1])) result = circuit() return result, np.shape(result) for name, tableau in [ ("default.qubit", None), ("default.clifford", True), ("default.clifford", False), ]: result, shape = run(name, tableau) label = name if tableau is None else f"{name}(tableau={tableau})" print(f"{label}:") print(" result:", result) print(" shape: ", shape)Tracebacks
System information
Existing GitHub issues