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 functionality provided by the WitnessCounter and some public functions for obtaining explicit costs for it.
In the following we'll use this program to demonstrate the utility of the witness:
from psiqworkbench import QPU, Qubits
from psiqworkbench.resource_estimation.witness_counter import WitnessFilter, WitnessCounter
from psiqworkbench import qubricks as qbk
import numpy as np
# >>buffer>> isn't actually needed, I included it so we can compare
# with qc.metrics later
qc = QPU(filters=[">>clean-ladder-filter>>", ">>single-control-filter>>", ">>buffer>>", ">>witness>>"])
qc.reset(200)
a = Qubits(35, 'a', qc)
b = Qubits(35, 'b', qc)
for i in range(100):
a.x(b) # throw in some uncompiled gates
qbk.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 enables various ways to extract cost metrics.
# 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.
One such piece of functionality is the ability to apply metrics functions to the witness to extract out a numerical representation of the witnessed costs. Eventually, this should replace the existing qc.metrics functionality, but for now we'll keep them separate while we converge on what we'd like the API to look like.
The main entrypoint for getting these costs out is the metrics method in witness_counter.witness_metrics_functions (not to be confused with the QPU method qc.metrics, although the similarity of the name should imply that at some point the new functionality will replace the old), which provides a single point of entry for getting costs out. We'll take a look at this method later, but for now let's look at some more modular cost functions.
The model of extensibility that this framework uses is that users can write their own functions similar to those that we will import here, that take a WitnessCounter and return a dictionary of costs.
For now, let's look at getting out some costs tailored to a baseline architecture:
from psiqworkbench.resource_estimation.witness_counter.witness_metrics_functions import get_active_volume_from_witness, get_baseline_costs_from_witness
witness_costs = get_baseline_costs_from_witness(qc.witness)
print("The resulting cost metrics: \n")
display(witness_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:
from psiqworkbench.resource_estimation.witness_counter.witness_metrics_functions import get_active_volume_from_witness, get_baseline_costs_from_witness
witness_av = get_active_volume_from_witness(witness)
print("The resulting AV cost metrics: \n")
display(witness_av)
The resulting AV cost metrics:
{'total_av': 643827}
But what do we do about QPU programs that have unsupported ops?
Currently, this is handled by the metrics functions making compilation assumptions about, e.g., how to decompose big stacked Toffolis 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 metrics function individually (or provide an alternative set of metrics 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. What's more, we can design our API such that users can specify which filters to apply during this process, thus allowing for different compilation filters to be passed to get different costs.
To see how this works, let's define a new QPU program with some unsupported ops:
# >>buffer>> isn't actually needed, I included it so we can compare
# with qc.metrics later
qc = QPU(filters=[">>buffer>>", ">>witness>>"])
qc.reset(200)
a = Qubits(35, 'a', qc)
b = Qubits(35, 'b', qc)
for i in range(100):
a.x(b) # throw in some uncompiled gates
qbk.GidneyAdd().compute(a, b) # and a Qubrick for good measure
b.y(a)
# and some potentially troublesome gates
a.rx(12)
b.reflect()
# a.swap(b) # swaps work with the witness, but they break the active volume counter currently
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 metrics function.
from psiqworkbench.resource_estimation.witness_counter.witness_metrics_functions import metrics
print("Simple costs\n")
display(metrics(qc))
print("\nExpanded costs\n")
display(metrics(qc, expanded_costs=True))
Simple costs
{'qubit_highwater_lower_bound': 104,
'qubit_highwater_upper_bound': 138,
'toffoli_count': 10233,
'measurement_count': 10232,
'rotation_count': 35,
't_count': 0,
'active_volume': 642940,
'total_num_ops': 37466}
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}