Qiskit / qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.
https://www.ibm.com/quantum/qiskit
Apache License 2.0
5.25k stars 2.37k forks source link

CircuitSampler error for TensoredOp measurement #8030

Open AlbertJP opened 2 years ago

AlbertJP commented 2 years ago

Environment

What is happening?

I am trying to sample a circuit containing two tensored measurements on a state: a circuit measurement (containing just an isometry) and a PauliSumOp measurement.

See below for the script. This is a print of the state function it generates:

ComposedOp([
  TensoredOp([
    CircuitMeasurement(
       ┌──────────┐
    q: ┤ Isometry ├
       └──────────┘
    ),
    ComposedOp([
      OperatorMeasurement(0.25 * I
      + 0.25 * Z),
      I
    ])
  ]),
  CircuitStateFn(
       ┌───┐
  q_0: ┤ H ├
       ├───┤
  q_1: ┤ H ├
       └───┘
  )
])

This is a print of the circuit sampler, which looks entirely correct:

ComposedOp([
  TensoredOp([
    MeasurementVector(Statevector([0.70710678+0.00000000e+00j, 0.70710678-8.65956056e-17j],
                dims=(2,))),
    OperatorMeasurement(0.25 * I
    + 0.25 * Z)
  ]),
  VectorStateFn(Statevector([0.5+0.j, 0.5+0.j, 0.5+0.j, 0.5+0.j],
              dims=(2, 2)))
])

Evaluating this CircuitSampler gives:

Traceback (most recent call last):
  File "./sample_composed_op.py", line 33, in <module>
    print(sampler.eval())
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/composed_op.py", line 141, in eval
    return reduce(tree_recursive_eval, reversed(eval_list))
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/composed_op.py", line 131, in tree_recursive_eval
    return l_arg.eval(r)
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/tensored_op.py", line 85, in eval
    return cast(Union[OperatorBase, complex], self.to_matrix_op().eval(front=front))
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/tensored_op.py", line 85, in eval
    return cast(Union[OperatorBase, complex], self.to_matrix_op().eval(front=front))
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/tensored_op.py", line 85, in eval
    return cast(Union[OperatorBase, complex], self.to_matrix_op().eval(front=front))
  [Previous line repeated 975 more times]
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/list_op.py", line 547, in to_matrix_op
    self.__class__(
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/tensored_op.py", line 93, in reduce
    reduced_ops = reduce(lambda x, y: x.tensor(y), reduced_ops) * self.coeff
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/tensored_op.py", line 93, in <lambda>
    reduced_ops = reduce(lambda x, y: x.tensor(y), reduced_ops) * self.coeff
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/state_fns/vector_state_fn.py", line 145, in tensor
    return TensoredOp([self, other])
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/tensored_op.py", line 46, in __init__
    super().__init__(oplist, combo_fn=partial(reduce, np.kron), coeff=coeff, abelian=abelian)
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/list_op.py", line 77, in __init__
    self._oplist = self._check_input_types(oplist)
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/list_op.py", line 84, in _check_input_types
    if all(isinstance(x, OperatorBase) for x in oplist):
  File "/home/albert/.local/lib/python3.8/site-packages/qiskit/opflow/list_ops/list_op.py", line 84, in <genexpr>
    if all(isinstance(x, OperatorBase) for x in oplist):
  File "/usr/lib/python3.8/abc.py", line 98, in __instancecheck__
    return _abc_instancecheck(cls, instance)
RecursionError: maximum recursion depth exceeded in comparison

It looks like there is a problem evaluating the TensoredOp. Both parts of it on their own evaluate correctly.

How can we reproduce the issue?

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import numpy as np
from qiskit import QuantumCircuit
from qiskit.opflow import StateFn, PauliExpectation, CircuitSampler, I, Z
from qiskit.providers.aer import AerSimulator
from qiskit.utils import QuantumInstance

x0=np.ones(2)/np.sqrt(2)
x0_circ = QuantumCircuit(1)
x0_circ.isometry(x0,[0],None)

op = .25 * (I + Z)

state = QuantumCircuit(2)
state.h(0)
state.h(1)

state_fn = (StateFn(x0_circ, is_measurement=True) ^ StateFn(op, is_measurement=True)) @ StateFn(state)
print('state function:')
print(state_fn)

backend=AerSimulator(method='statevector')
instance = QuantumInstance(backend=backend)
sampler = CircuitSampler(instance).convert(state_fn)
print('')
print('sampler:')
print(sampler)

print('')
print('result:')
print(sampler.eval())

What should happen?

Either a (numerical) result should come out of this, or a sensible error message if I'm trying to do something impossible.

Any suggestions?

No response

AlbertJP commented 2 years ago

FWIW: making a SummedOp (instead of TensoredOp) of a CircuitMeasurement and an OperatorMeasurement is not a problem.

Cryoris commented 2 years ago

Thanks for the report! Is there a reason you want to tensor the measurements instead of creating a measurement operator of the tensored operators (i.e. what you suggested in your second comment)?

AlbertJP commented 2 years ago

Do you mean something like StateFn(op_1 ^ op_2, is_measurement=True)?

AlbertJP commented 2 years ago

This doesn't work either. If I replace line 20 by this:

state_fn = StateFn(StateFn(x0_circ) ^ StateFn(op), is_measurement=True) @ StateFn(state)

the same error appears.

Cryoris commented 2 years ago

I mean first construct the tensored operator and then convert it into a measurement by wrapping it with StateFn(..., is_measurement=True). So in your case this would be

state_fn = StateFn(CircuitOp(x0_circ) ^ op, is_measurement=True) @ StateFn(state)

🙂

AlbertJP commented 2 years ago

Yes, that works. Thanks for the CircuitOp suggestion, that makes the difference here, it allows me to tensor both operators before creating the StateFn. Nevertheless, is it a bug that two measurements cannot be tensored or is this by design?

AlbertJP commented 2 years ago

^ hit the wrong button by mistake, apologies

Cryoris commented 2 years ago

It seems to be a bug, because TensoredOp.eval first calls self.to_matrix_op, which will produce a TensoredOp of matrices, and then calls eval again. But since the type is still a TensoredOp we call self.to_matrix_op again and then eval again.... and that results in the infinite recursion you see above.