5

Earlier it was very easy and straightforward to get counts for all classical registers using just one line of code:

counts = backend.run(qc, shots=1000).result().get_counts()

However, when I run such code on QPU I'm getting a warning that such call is deprecated and will be removed soon and a link to a documentation how to use the new SamplerV2 instead.

Using SamplerV2 I can get the 'combined' counts in such way:

job = sampler.run([qc])
result = job.result()[0]
counts = result.data.meas.get_counts()

The problem is that if I want to use the meas property I need to invoke measure_all() on the circuit. Otherwise I have to obtain count for each register individually in such way:

counts_c_reg0 = result.data.c_reg0.get_counts()

However, such approach brings some problems. First of all, it is quite messy and error-prone to use a register name as a property in results. Also for my algo the value of one register measurement has no sense and I need to analyse the output in total for all registers (for example, how many times 101 was measured in one shot and so on). Additionally I can't use measure_all() as measurements happen on my circuit on different stages.

How can I get combined counts when using qiskit_ibm_runtime.SamplerV2? Or using any other 'modern' approach without having 'deprecated' warnings.

chm
  • 153
  • 3

2 Answers2

5

Great question. I struggled with this myself recently.

Luckily, there is a simple solution, which unfortunately isn’t easy to find since it has not been used in any demos/tutorials. You can use the .join_data method from the SamplerPubResult class as follows:

job = sampler.run([qc])
result = job.result()[0]
counts = result.join_data().get_counts()

This will combine the results of all registers. And the nice thing is that, even if you only have one register in your circuit, you don't need to specify its name as an attribute to extract the result.


Getting counts for separate registers

I know your question was explicitly about combined counts, but in case you want to extract separate register results and still "avoid" the messiness of dealing with register names explicitly, you can do the following:

counts_dict = {}
for i, pub_res in enumerate(result):
    for key, val in pub_res.data.items(): 
        reg_name = f'cir_{i}_{key}'
        counts_dict[reg_name] = val.get_counts()
  • The first for loop will iterate over each PUB result.
  • The second for loop will iterate over each of the register results, which are stored in a dictionary-like object where they keys are the register names, and the values have the experiment results.
  • I store all results in another dictionary where the keys are strings with the circuit number and the register name, but of course you can change this to whatever works best for your needs.
  • You will need an extra for loop if you have parametrized circuits in order to iterate over each set of parameters.
diemilio
  • 3,043
  • 1
  • 6
  • 16
0

@diemilio answers your direct question of "How can I get combined counts when using qiskit_ibm_runtime.SamplerV2?". Let me instead comment on

for my algo the value of one register measurement has no sense and I need to analyse the output in total for all registers (for example, how many times 101 was measured in one shot and so on)

Say you have a circuit

from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister

circuit = QuantumCircuit( QuantumRegister(5), alpha:=ClassicalRegister(2, "alpha") beta:=ClassicalRegister(2, "beta" ) circuit.measure([0, 1, 2], alpha) circuit.measure([3, 4], beta)

with SamplerV2 result data

data = job.result()[0].data

Here, data is a dict-like (with additional attr access) that maps the register names you chose in your quantum program to corresponding shot-level arrays of data for each register. In particular, dictionaries are not part of the data model, where get_counts() (and likewise join_data from the other answer) is provided as a convenience for those who prefer to work with the dictionary format.

The array data is a NumPy array whose last axis is over bytes (whose bits are the bitstrings you're used to), the second last axis is over ordered shots, and the leading axes correspond to the "shapes" of your inputs to the sampler (the sampler has native support for broadcasting circuit Parameters against nd-arrays of parameter values).

If you want to work directly with the array data, you can, and it will typically be more performant for large datasets. For example, if you want to select the entries of beta whose corresponding shots in alpha are "101",

# get indices of alpha whose raw data is the bitstring "101"
idxs = data.alpha.array == 0b101

select the corresponding entries of beta

postselected_beta = data.beta[idxs[:, 0]]

do something with the data

evs = postselected_beta.expectation_values(["ZZ", "ZI", "II"]) counts = postselected_beta.get_counts() ...

ihincks
  • 101
  • 1