3
$\begingroup$

I am using Qiskit's opflow and its CircuitSampler to evaluate matrix elements / expectation values. Now, going beyond statevector and simple qasm simulations, I want to introduce noise of a real device, specify qubit-connectivity (coupling_map), and choose which physical qubits the logical qubits of my circuit are mapped to (initial_layout). This is all specified in a QuantumInstance which is then passed to the CircuitSampler,

result = CircuitSampler(QuantumInstance).convert(operator).eval()

How do all these specifications for transpilation translate through the CircuitSampler and into the final output? I.e., does CircuitSampler transpile the circuits inside, e.g., ListOp before sampling? And how can I check which physical qubits the logical ones were mapped to during transpilation?

I am confused by this sentence in the documentation:

The CircuitSampler aggressively caches transpiled circuits to handle re-parameterization of the same circuit efficiently.

What does this mean?

$\endgroup$
4
$\begingroup$

This post contains a few questions, so let's first recap the steps the circuit sampler goes through (you can also check out the source code here), before answering your questions.

How the Circuit Sampler works

Provided with a backend/quantum instance and an operator expression, the job of the circuit sampler is to execute all circuits in the operator expression and replace them by the circuit result. Imagine you have an operator expression consisting of an operator measurement composed with a circuit-state:

circuit = QuantumCircuit(1)
circuit.h(0)

hamiltonian = X + Z

expr = StateFn(hamiltonian, is_measurement=True).compose(state)
print(expr)
# prints:
# ComposedOp([
#   OperatorMeasurement(1.0 * X
#   + 1.0 * Z),
#   CircuitStateFn(
#        ┌───┐
#   q_0: ┤ H ├
#        └───┘
#   )
# ])

If you then apply the circuit sampler (e.g. with a statevector simulator as backend)

backend = Aer.get_backend('statevector_simulator')
sampler = CircuitSampler(backend) 
sampled = sampler.convert(expr)
print(sampled)
# prints:
# ComposedOp([
#   OperatorMeasurement(1.0 * X
#   + 1.0 * Z),
#   VectorStateFn(Statevector([0.70710678+0.j, 0.70710678+0.j], dims=(2,)))
# ])

then sampled is the same as expr but the circuit-state has been replaced with a vector-state.

To do that, the circuit sampler goes through the following steps:

  1. Extract all circuits from the operator expression (and cache them)
  2. Transpile all circuits (and cache them)
  3. Execute all circuits with the provided quantum instance
  4. Replace the original circuit-states with the results

Your questions

So to answer your questions above:

How do all these specifications for transpilation translate through the CircuitSampler and into the final output?

Yes, the circuit sampler transpiles the circuits according to the specifications in the quantum instance.

And how can I check which physical qubits the logical ones were mapped to during transpilation?

There is no nice way to check what the transpiled circuits look like. Ideally you would just transpile the circuits manually with the quantum instance since the circuit sampler's purpose is only the execution. However, you could access the cache (but that's not officially supported behavior):

last_cache = list(sampler._cached_ops.values())[-1]
transpiled_circs = last_cache.transpiled_circ_cache

I am confused by this sentence in the documentation:

The CircuitSampler aggressively caches transpiled circuits to handle re-parameterization of the same circuit efficiently.

We often have the use case where we want to execute the same circuit many times but with different parameter values in the qubit gates. If we only change the parameter values, we can transpile a "template" and only assign the values afterwards. Since transpilation is expensive, this can save a lot of time.

As pointed out above in the circuit sampler steps, the circuits sampler caches circuits at different points and tries to re-use already transpiled circuits to avoid the expensive transpilation.

$\endgroup$
6
  • $\begingroup$ Thank you for this very comprehensive answer! Manually transpiled circuits would not be re-transpiled by the CircuitSampler? $\endgroup$
    – Durd3nT
    May 10 at 8:08
  • $\begingroup$ Yes, they would, but re-transpiling already transpiled circuits is very cheap since they are already in the correct basis gate set and optimized. $\endgroup$
    – Cryoris
    May 10 at 8:10
  • $\begingroup$ @Cryoris How can I send multiple circuits through the CircuitSampler? I am calculating expectation values for VQE and I want all of my circuits for each iteration sent to the IBMQ simultaneously to reduce queue times. I tried sampler = CircuitSampler(q_instance).convert(expectation) where expectation is a list but I receive a "'list' object has no attribute 'to_circuit_op'" error. $\endgroup$ Jun 15 at 16:44
  • 1
    $\begingroup$ If you wrap the list into a ListOp it should work. $\endgroup$
    – Cryoris
    Jun 16 at 21:11
  • 1
    $\begingroup$ Only if you pass the parameters as the second argument in CircuitSampler.convert, otherwise it won't be able to recognize that it has seen a similar circuit before already. $\endgroup$
    – Cryoris
    Jun 22 at 15:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.