A demo of the WitnessCounter object¶
When analyzing quantum programs, we can use the WitnessCounter to obtain a compressed representation of the QRE-relevant aspects of some QPU program. This notebook gives a user-focused overview of the WitnessCounter itself, plus how to read explicit costs out of a program via the resource_estimator interface (which is backed by the witness under the hood).
In the following we'll use this program to demonstrate the utility of the witness:
from psiqworkbench import QPU, Qubits
from psiqworkbench.qubricks import GidneyAdd
qc = QPU(num_qubits=200, filters=[">>clean-ladder-filter>>", ">>single-control-filter>>", ">>witness>>"])
a = Qubits(35, 'a', qc)
b = Qubits(35, 'b', qc)
for i in range(100):
a.x(b) # throw in some uncompiled gates
GidneyAdd().compute(a, b) # and a Qubrick for good measure
b.y(a)
# and some potentially troublesome gates
a.rx(12)
a.ppr(30, 2, 1)
b.ppm(3, 5, 7)
b.reflect()
a.swap(b)
The witness object can be accessed by default, directly passing witness into the filter pipeline, or by using the convenience alias provided in qpu.py. When printed, it displays a neatly formatted summary of the observed operations.
witness = qc.witness
print(witness)
TOTAL METRICS SUMMARY:
----------------------
rotations: 35
t_gates: 0
measurements: 0
gidney_lelbows: 10232
gidney_relbows: 10232
toffs: 1
pprs: 1
ppr_av: 876
ppms: 1
ppm_av: 11
single_qubit_cliffords:0
two_qubit_cliffords: 27100
active_volume: 643827
black_box_av: 0
single_qubit_clifford_av:0
two_qubit_clifford_av:110900
uncontrolled_non_clifford_av:30625
singly_controlled_non_clifford_av:0
two_controlled_non_clifford_av:501415
WITNESS BREAKDOWN:
------------------
QPU ops:
--------
OP: (name = qc.lelbow, target = 1, condition = 2, reactive = 0)
COUNT: 10232
METRICS:
gidney_lelbows: cost per op: 1 total cost: 10232
active_volume: cost per op: 44 total cost: 450208
two_controlled_non_clifford_av: cost per op: 44 total cost: 450208
OP: (name = qc.ppm, x_weight = 0, z_weight = 1, sign = 3, reactive = 0, y_weight = 2)
COUNT: 1
METRICS:
ppms: cost per op: 1 total cost: 1
ppm_av: cost per op: 11 total cost: 11
active_volume: cost per op: 11 total cost: 11
OP: (name = qc.ppr, x_weight = 1, z_weight = 1, theta = 30, reactive = 0, condition = 0, error_param = 0, y_weight = 0)
COUNT: 1
METRICS:
pprs: cost per op: 1 total cost: 1
ppr_av: cost per op: 876 total cost: 876
active_volume: cost per op: 876 total cost: 876
OP: (name = qc.relbow, target = 1, condition = 2, reactive = 0)
COUNT: 10232
METRICS:
gidney_relbows: cost per op: 1 total cost: 10232
active_volume: cost per op: 5 total cost: 51160
two_controlled_non_clifford_av: cost per op: 5 total cost: 51160
OP: (name = qc.rx, target = 35, condition = 0, theta = 12, reactive = 0, error_param = 0)
COUNT: 1
METRICS:
rotations: cost per op: 35 total cost: 35
active_volume: cost per op: 30625 total cost: 30625
uncontrolled_non_clifford_av: cost per op: 30625 total cost: 30625
OP: (name = qc.swap, target = 2, condition = 0, theta = 0, reactive = 0, error_param = 0)
COUNT: 35
OP: (name = qc.x, target = 1, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 13500
METRICS:
two_qubit_cliffords: cost per op: 1 total cost: 13500
active_volume: cost per op: 4 total cost: 54000
two_qubit_clifford_av: cost per op: 4 total cost: 54000
OP: (name = qc.x, target = 2, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 3300
METRICS:
two_qubit_cliffords: cost per op: 2 total cost: 6600
active_volume: cost per op: 6 total cost: 19800
two_qubit_clifford_av: cost per op: 6 total cost: 19800
OP: (name = qc.x, target = 35, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 100
METRICS:
two_qubit_cliffords: cost per op: 35 total cost: 3500
active_volume: cost per op: 56 total cost: 5600
two_qubit_clifford_av: cost per op: 56 total cost: 5600
OP: (name = qc.y, target = 35, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 100
METRICS:
two_qubit_cliffords: cost per op: 35 total cost: 3500
active_volume: cost per op: 315 total cost: 31500
two_qubit_clifford_av: cost per op: 315 total cost: 31500
OP: (name = qc.z, target = 0, condition = 3, theta = 0, reactive = 0, error_param = 0)
COUNT: 1
METRICS:
toffs: cost per op: 1 total cost: 1
active_volume: cost per op: 47 total cost: 47
two_controlled_non_clifford_av: cost per op: 47 total cost: 47
Qubrick ops:
------------
OP: (name = qc.qbk_compute_end, target = 0, condition = 0, reactive = 0, label = CleanLadder)
COUNT: 201
OP: (name = qc.qbk_compute_end, target = 0, condition = 0, reactive = 0, label = GidneyAdd)
COUNT: 100
OP: (name = qc.qbk_compute_end, target = 0, condition = 0, reactive = 0, label = Reflect)
COUNT: 1
OP: (name = qc.qbk_compute_start, target = 0, condition = 0, reactive = 0, label = CleanLadder)
COUNT: 201
OP: (name = qc.qbk_compute_start, target = 0, condition = 0, reactive = 0, label = GidneyAdd)
COUNT: 100
OP: (name = qc.qbk_compute_start, target = 0, condition = 0, reactive = 0, label = Reflect)
COUNT: 1
OP: (name = qc.qbk_uncompute_end, target = 0, condition = 0, reactive = 0, label = CleanLadder)
COUNT: 201
OP: (name = qc.qbk_uncompute_start, target = 0, condition = 0, reactive = 0, label = CleanLadder)
COUNT: 201
Other ops:
------------
OP: (name = qc.qubits_alloc, target = 32, reactive = 0)
COUNT: 1
OP: (name = qc.qubits_alloc, target = 34, reactive = 0)
COUNT: 300
OP: (name = qc.qubits_alloc, target = 35, reactive = 0)
COUNT: 2
OP: (name = qc.qubits_free, target = 32, reactive = 0)
COUNT: 1
OP: (name = qc.qubits_free, target = 34, reactive = 0)
COUNT: 300
OP: (name = qc.reset, num_qubits = 200)
COUNT: 1
There are many benefits to using a dedicated class to store these operations instead of using a simple dictionary. First, it enables elegant printing; second, it encapsulates the operations within a unified data structure; and third, it underpins resource estimation by capturing the QRE-relevant ops in a form that the resource estimator can consume.
# we can filter to get all x gates
print(witness.filter("x"))
# using just "x" or "qc.x"
print(witness.filter("qc.x"))
# or using the opcode if you prefer
from psiqworkbench.ops.qpu_ops import OP_qc_x
print(witness.filter(OP_qc_x))
# Alternatively, maybe we want to see all 2-condition ops:
print(witness.filter(condition=2))
# or how about just 2 target x gates?
print(witness.filter("x", target=2))
# or what if we want all gates with at least one condition?
print(witness.filter(condition=lambda x: x > 0))
# We can check that we didn't get any ops with more than 2 conditions
print(witness.filter(condition=lambda x: x > 2))
TOTAL METRICS SUMMARY:
----------------------
rotations: 0
t_gates: 0
measurements: 0
gidney_lelbows: 0
gidney_relbows: 0
toffs: 0
pprs: 0
ppr_av: 0
ppms: 0
ppm_av: 0
single_qubit_cliffords:0
two_qubit_cliffords: 23600
active_volume: 79400
black_box_av: 0
single_qubit_clifford_av:0
two_qubit_clifford_av:79400
uncontrolled_non_clifford_av:0
singly_controlled_non_clifford_av:0
two_controlled_non_clifford_av:0
WITNESS BREAKDOWN:
------------------
QPU ops:
--------
OP: (name = qc.x, target = 1, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 13500
METRICS:
two_qubit_cliffords: cost per op: 1 total cost: 13500
active_volume: cost per op: 4 total cost: 54000
two_qubit_clifford_av: cost per op: 4 total cost: 54000
OP: (name = qc.x, target = 2, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 3300
METRICS:
two_qubit_cliffords: cost per op: 2 total cost: 6600
active_volume: cost per op: 6 total cost: 19800
two_qubit_clifford_av: cost per op: 6 total cost: 19800
OP: (name = qc.x, target = 35, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 100
METRICS:
two_qubit_cliffords: cost per op: 35 total cost: 3500
active_volume: cost per op: 56 total cost: 5600
two_qubit_clifford_av: cost per op: 56 total cost: 5600
TOTAL METRICS SUMMARY:
----------------------
rotations: 0
t_gates: 0
measurements: 0
gidney_lelbows: 0
gidney_relbows: 0
toffs: 0
pprs: 0
ppr_av: 0
ppms: 0
ppm_av: 0
single_qubit_cliffords:0
two_qubit_cliffords: 23600
active_volume: 79400
black_box_av: 0
single_qubit_clifford_av:0
two_qubit_clifford_av:79400
uncontrolled_non_clifford_av:0
singly_controlled_non_clifford_av:0
two_controlled_non_clifford_av:0
WITNESS BREAKDOWN:
------------------
QPU ops:
--------
OP: (name = qc.x, target = 1, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 13500
METRICS:
two_qubit_cliffords: cost per op: 1 total cost: 13500
active_volume: cost per op: 4 total cost: 54000
two_qubit_clifford_av: cost per op: 4 total cost: 54000
OP: (name = qc.x, target = 2, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 3300
METRICS:
two_qubit_cliffords: cost per op: 2 total cost: 6600
active_volume: cost per op: 6 total cost: 19800
two_qubit_clifford_av: cost per op: 6 total cost: 19800
OP: (name = qc.x, target = 35, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 100
METRICS:
two_qubit_cliffords: cost per op: 35 total cost: 3500
active_volume: cost per op: 56 total cost: 5600
two_qubit_clifford_av: cost per op: 56 total cost: 5600
TOTAL METRICS SUMMARY:
----------------------
rotations: 0
t_gates: 0
measurements: 0
gidney_lelbows: 0
gidney_relbows: 0
toffs: 0
pprs: 0
ppr_av: 0
ppms: 0
ppm_av: 0
single_qubit_cliffords:0
two_qubit_cliffords: 23600
active_volume: 79400
black_box_av: 0
single_qubit_clifford_av:0
two_qubit_clifford_av:79400
uncontrolled_non_clifford_av:0
singly_controlled_non_clifford_av:0
two_controlled_non_clifford_av:0
WITNESS BREAKDOWN:
------------------
QPU ops:
--------
OP: (name = qc.x, target = 1, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 13500
METRICS:
two_qubit_cliffords: cost per op: 1 total cost: 13500
active_volume: cost per op: 4 total cost: 54000
two_qubit_clifford_av: cost per op: 4 total cost: 54000
OP: (name = qc.x, target = 2, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 3300
METRICS:
two_qubit_cliffords: cost per op: 2 total cost: 6600
active_volume: cost per op: 6 total cost: 19800
two_qubit_clifford_av: cost per op: 6 total cost: 19800
OP: (name = qc.x, target = 35, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 100
METRICS:
two_qubit_cliffords: cost per op: 35 total cost: 3500
active_volume: cost per op: 56 total cost: 5600
two_qubit_clifford_av: cost per op: 56 total cost: 5600
TOTAL METRICS SUMMARY:
----------------------
rotations: 0
t_gates: 0
measurements: 0
gidney_lelbows: 10232
gidney_relbows: 10232
toffs: 0
pprs: 0
ppr_av: 0
ppms: 0
ppm_av: 0
single_qubit_cliffords:0
two_qubit_cliffords: 0
active_volume: 501368
black_box_av: 0
single_qubit_clifford_av:0
two_qubit_clifford_av:0
uncontrolled_non_clifford_av:0
singly_controlled_non_clifford_av:0
two_controlled_non_clifford_av:501368
WITNESS BREAKDOWN:
------------------
QPU ops:
--------
OP: (name = qc.lelbow, target = 1, condition = 2, reactive = 0)
COUNT: 10232
METRICS:
gidney_lelbows: cost per op: 1 total cost: 10232
active_volume: cost per op: 44 total cost: 450208
two_controlled_non_clifford_av: cost per op: 44 total cost: 450208
OP: (name = qc.relbow, target = 1, condition = 2, reactive = 0)
COUNT: 10232
METRICS:
gidney_relbows: cost per op: 1 total cost: 10232
active_volume: cost per op: 5 total cost: 51160
two_controlled_non_clifford_av: cost per op: 5 total cost: 51160
TOTAL METRICS SUMMARY:
----------------------
rotations: 0
t_gates: 0
measurements: 0
gidney_lelbows: 0
gidney_relbows: 0
toffs: 0
pprs: 0
ppr_av: 0
ppms: 0
ppm_av: 0
single_qubit_cliffords:0
two_qubit_cliffords: 6600
active_volume: 19800
black_box_av: 0
single_qubit_clifford_av:0
two_qubit_clifford_av:19800
uncontrolled_non_clifford_av:0
singly_controlled_non_clifford_av:0
two_controlled_non_clifford_av:0
WITNESS BREAKDOWN:
------------------
QPU ops:
--------
OP: (name = qc.x, target = 2, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 3300
METRICS:
two_qubit_cliffords: cost per op: 2 total cost: 6600
active_volume: cost per op: 6 total cost: 19800
two_qubit_clifford_av: cost per op: 6 total cost: 19800
TOTAL METRICS SUMMARY:
----------------------
rotations: 0
t_gates: 0
measurements: 0
gidney_lelbows: 10232
gidney_relbows: 10232
toffs: 1
pprs: 0
ppr_av: 0
ppms: 0
ppm_av: 0
single_qubit_cliffords:0
two_qubit_cliffords: 27100
active_volume: 612315
black_box_av: 0
single_qubit_clifford_av:0
two_qubit_clifford_av:110900
uncontrolled_non_clifford_av:0
singly_controlled_non_clifford_av:0
two_controlled_non_clifford_av:501415
WITNESS BREAKDOWN:
------------------
QPU ops:
--------
OP: (name = qc.lelbow, target = 1, condition = 2, reactive = 0)
COUNT: 10232
METRICS:
gidney_lelbows: cost per op: 1 total cost: 10232
active_volume: cost per op: 44 total cost: 450208
two_controlled_non_clifford_av: cost per op: 44 total cost: 450208
OP: (name = qc.relbow, target = 1, condition = 2, reactive = 0)
COUNT: 10232
METRICS:
gidney_relbows: cost per op: 1 total cost: 10232
active_volume: cost per op: 5 total cost: 51160
two_controlled_non_clifford_av: cost per op: 5 total cost: 51160
OP: (name = qc.x, target = 1, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 13500
METRICS:
two_qubit_cliffords: cost per op: 1 total cost: 13500
active_volume: cost per op: 4 total cost: 54000
two_qubit_clifford_av: cost per op: 4 total cost: 54000
OP: (name = qc.x, target = 2, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 3300
METRICS:
two_qubit_cliffords: cost per op: 2 total cost: 6600
active_volume: cost per op: 6 total cost: 19800
two_qubit_clifford_av: cost per op: 6 total cost: 19800
OP: (name = qc.x, target = 35, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 100
METRICS:
two_qubit_cliffords: cost per op: 35 total cost: 3500
active_volume: cost per op: 56 total cost: 5600
two_qubit_clifford_av: cost per op: 56 total cost: 5600
OP: (name = qc.y, target = 35, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 100
METRICS:
two_qubit_cliffords: cost per op: 35 total cost: 3500
active_volume: cost per op: 315 total cost: 31500
two_qubit_clifford_av: cost per op: 315 total cost: 31500
OP: (name = qc.z, target = 0, condition = 3, theta = 0, reactive = 0, error_param = 0)
COUNT: 1
METRICS:
toffs: cost per op: 1 total cost: 1
active_volume: cost per op: 47 total cost: 47
two_controlled_non_clifford_av: cost per op: 47 total cost: 47
TOTAL METRICS SUMMARY:
----------------------
rotations: 0
t_gates: 0
measurements: 0
gidney_lelbows: 0
gidney_relbows: 0
toffs: 1
pprs: 0
ppr_av: 0
ppms: 0
ppm_av: 0
single_qubit_cliffords:0
two_qubit_cliffords: 0
active_volume: 47
black_box_av: 0
single_qubit_clifford_av:0
two_qubit_clifford_av:0
uncontrolled_non_clifford_av:0
singly_controlled_non_clifford_av:0
two_controlled_non_clifford_av:47
WITNESS BREAKDOWN:
------------------
QPU ops:
--------
OP: (name = qc.z, target = 0, condition = 3, theta = 0, reactive = 0, error_param = 0)
COUNT: 1
METRICS:
toffs: cost per op: 1 total cost: 1
active_volume: cost per op: 47 total cost: 47
two_controlled_non_clifford_av: cost per op: 47 total cost: 47
In all these cases, the returned value from WitnessCounter.filter() is itself an instance of WitnessCounter, meaning all the functionality that can be applied to the ops as a whole can be applied to these subsets of matching ops.
The recommended entry point for getting numerical costs out of a QPU program is the resource_estimator function in psiqworkbench.resource_estimation.qre, which returns a ResourceEstimator whose .resources() method exposes the top-level cost metrics. Pass expanded=True for a full readout (gate counts plus active-volume buckets and aggregations); omit it for the trimmed summary.
For now, let's look at the expanded baseline costs for our program:
from psiqworkbench.resource_estimation.qre import resource_estimator
costs = resource_estimator(qc).resources(expanded=True)
print("The resulting cost metrics: \n")
display(costs)
The resulting cost metrics:
{'qubit_highwater': 104,
'rotations': 35,
't_gates': 0,
'measurements': 0,
'gidney_lelbows': 10232,
'gidney_relbows': 10232,
'toffs': 1,
'pprs': 1,
'ppr_av': 876,
'ppms': 1,
'ppm_av': 11,
'single_qubit_cliffords': 0,
'two_qubit_cliffords': 27100,
'active_volume': 643827,
'black_box_av': 0,
'single_qubit_clifford_av': 0,
'two_qubit_clifford_av': 110900,
'uncontrolled_non_clifford_av': 30625,
'singly_controlled_non_clifford_av': 0,
'two_controlled_non_clifford_av': 501415,
'aggregated_toff_count': 10233,
'aggregated_measurement_count': 10233,
'aggregated_rotation_count': 36,
'aggregated_clifford_count': 27100,
'aggregated_clifford_av': 110900,
'aggregated_non_clifford_av': 532927,
'total_av': 643827,
'average_ppr_x_weight': 1.0,
'average_ppr_y_weight': 0.0,
'average_ppr_z_weight': 1.0,
'average_ppm_x_weight': 0.0,
'average_ppm_y_weight': 2.0,
'average_ppm_z_weight': 1.0,
'total_num_ops': 37503}
Now let's take a look at active volume:
witness_av = resource_estimator(qc).resources()["active_volume"]
print("The resulting AV cost metrics: \n")
display({"total_av": witness_av})
The resulting AV cost metrics:
{'total_av': 643827}
But what do we do about QPU programs that have unsupported ops?
A naive approach would be to make compilation assumptions inline in the cost-extraction code (e.g. hard-coding how big stacked Toffolis decompose into elbows), but this has a few issues:
- This logic is notorious for introducing hard-to-catch bugs for apps developers.
- It's restricted to only a single set of compilation assumptions – if we want to change our defaults from clean ladder decomposition to e.g. conditionally clean decomposition, we'd need to change every cost-extraction path individually (or provide an alternative set of cost functions written from scratch).
- It's a "one and done" affair – particularly for mini metrics, once we've computed our costs, we can't go back and use a different set of assumptions to compute new ones.
Rather than analyzing the ops and trying to catch all edge cases, we can make use of the fact that we have access to QPU ops to simplify the process: if we define a set of compilation filters that guarantee our final program will be compatible with whatever device we're interested in, we can pass our ops through those filters when calculating the costs and thus get a representative expression for the actual costs we would have got if we had properly specified our program to begin with. The resource estimator applies a default set of such filters automatically, but it can be overridden by adding extra filters.
To see how this works, let's define a new QPU program with some unsupported ops:
qc = QPU(num_qubits=200, filters=[">>witness>>"])
a = Qubits(35, 'a', qc)
b = Qubits(35, 'b', qc)
for i in range(100):
a.x(b) # throw in some uncompiled gates
GidneyAdd().compute(a, b) # and a Qubrick for good measure
b.y(a)
# and some potentially troublesome gates
a.rx(12)
b.reflect()
As the witness shows, the big multi-controlled gates are recorded as being completely uncompiled:
print(qc.witness)
TOTAL METRICS SUMMARY:
----------------------
rotations: 35
t_gates: 0
measurements: 0
gidney_lelbows: 3400
gidney_relbows: 3400
toffs: 0
pprs: 0
ppr_av: 0
ppms: 0
ppm_av: 0
single_qubit_cliffords:0
two_qubit_cliffords: 20100
active_volume: 271025
black_box_av: 0
single_qubit_clifford_av:0
two_qubit_clifford_av:73800
uncontrolled_non_clifford_av:30625
singly_controlled_non_clifford_av:0
two_controlled_non_clifford_av:166600
WITNESS BREAKDOWN:
------------------
QPU ops:
--------
OP: (name = qc.lelbow, target = 1, condition = 2, reactive = 0)
COUNT: 3400
METRICS:
gidney_lelbows: cost per op: 1 total cost: 3400
active_volume: cost per op: 44 total cost: 149600
two_controlled_non_clifford_av: cost per op: 44 total cost: 149600
OP: (name = qc.relbow, target = 1, condition = 2, reactive = 0)
COUNT: 3400
METRICS:
gidney_relbows: cost per op: 1 total cost: 3400
active_volume: cost per op: 5 total cost: 17000
two_controlled_non_clifford_av: cost per op: 5 total cost: 17000
OP: (name = qc.rx, target = 35, condition = 0, theta = 12, reactive = 0, error_param = 0)
COUNT: 1
METRICS:
rotations: cost per op: 35 total cost: 35
active_volume: cost per op: 30625 total cost: 30625
uncontrolled_non_clifford_av: cost per op: 30625 total cost: 30625
OP: (name = qc.x, target = 1, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 13500
METRICS:
two_qubit_cliffords: cost per op: 1 total cost: 13500
active_volume: cost per op: 4 total cost: 54000
two_qubit_clifford_av: cost per op: 4 total cost: 54000
OP: (name = qc.x, target = 2, condition = 1, theta = 0, reactive = 0, error_param = 0)
COUNT: 3300
METRICS:
two_qubit_cliffords: cost per op: 2 total cost: 6600
active_volume: cost per op: 6 total cost: 19800
two_qubit_clifford_av: cost per op: 6 total cost: 19800
OP: (name = qc.x, target = 35, condition = 35, theta = 0, reactive = 0, error_param = 0)
COUNT: 100
METRICS:
active_volume: cost per op: None total cost: None
OP: (name = qc.y, target = 35, condition = 35, theta = 0, reactive = 0, error_param = 0)
COUNT: 100
METRICS:
active_volume: cost per op: None total cost: None
OP: (name = qc.z, target = 0, condition = 35, theta = 0, reactive = 0, error_param = 0)
COUNT: 1
METRICS:
active_volume: cost per op: None total cost: None
Qubrick ops:
------------
OP: (name = qc.qbk_compute_end, target = 0, condition = 0, reactive = 0, label = GidneyAdd)
COUNT: 100
OP: (name = qc.qbk_compute_end, target = 0, condition = 0, reactive = 0, label = Reflect)
COUNT: 1
OP: (name = qc.qbk_compute_start, target = 0, condition = 0, reactive = 0, label = GidneyAdd)
COUNT: 100
OP: (name = qc.qbk_compute_start, target = 0, condition = 0, reactive = 0, label = Reflect)
COUNT: 1
Other ops:
------------
OP: (name = qc.qubits_alloc, target = 34, reactive = 0)
COUNT: 100
OP: (name = qc.qubits_alloc, target = 35, reactive = 0)
COUNT: 2
OP: (name = qc.qubits_free, target = 34, reactive = 0)
COUNT: 100
OP: (name = qc.reset, num_qubits = 200)
COUNT: 1
To begin, let's define a function that will tell us if the witness we've built contains some unsupported ops. This is very easy to do with the new witness_counter.filter() functionality, since we can explicitly define conditions that violate our requirements and simply check that the witness doesn't contain any ops matching those conditions. A function demonstrating this is shown below (keep in mind this is only a demo – the compilation team should be responsible for writing and checking the conditions against which the ops are checked).
In this case we do have unsupported ops, so when we call witness.validate(), we expect it to return False:
qc.witness.validate()
False
After verifying that the QPU object contains unsupported ops, we can pass the witness' ops through a minimal set of compilation filters, building a new witness that we can then return. This is handled under the hood by the resource estimator.
estimator = resource_estimator(qc)
print("Simple costs\n")
display(estimator.resources())
print("\nExpanded costs\n")
display(estimator.resources(expanded=True))
Simple costs
{'active_volume': 642940,
'gidney_lelbows': 10232,
'gidney_relbows': 10232,
'measurements': 0,
'rotations': 35,
'pprs': 0,
'ppms': 0,
't_gates': 0,
'toffs': 1,
'qubit_highwater': 104}
Expanded costs
{'qubit_highwater_lower_bound': 104,
'qubit_highwater_upper_bound': 138,
'rotations': 35,
't_gates': 0,
'measurements': 0,
'gidney_lelbows': 10232,
'gidney_relbows': 10232,
'toffs': 1,
'pprs': 0,
'ppr_av': 0,
'ppms': 0,
'ppm_av': 0,
'single_qubit_cliffords': 0,
'two_qubit_cliffords': 27100,
'active_volume': 642940,
'black_box_av': 0,
'single_qubit_clifford_av': 0,
'two_qubit_clifford_av': 110900,
'uncontrolled_non_clifford_av': 30625,
'singly_controlled_non_clifford_av': 0,
'two_controlled_non_clifford_av': 501415,
'aggregated_toff_count': 10233,
'aggregated_measurement_count': 10232,
'aggregated_rotation_count': 35,
'aggregated_clifford_count': 27100,
'aggregated_clifford_av': 110900,
'aggregated_non_clifford_av': 532040,
'total_av': 642940,
'average_ppr_x_weight': 0,
'average_ppr_y_weight': 0,
'average_ppr_z_weight': 0,
'average_ppm_x_weight': 0,
'average_ppm_y_weight': 0,
'average_ppm_z_weight': 0,
'total_num_ops': 37466}