cda-tum / mqt-qcec Goto Github PK
View Code? Open in Web Editor NEWMQT QCEC - A tool for Quantum Circuit Equivalence Checking
Home Page: https://mqt.readthedocs.io/projects/qcec
License: MIT License
MQT QCEC - A tool for Quantum Circuit Equivalence Checking
Home Page: https://mqt.readthedocs.io/projects/qcec
License: MIT License
Hi,
I can't understand the concept of function applyGate from the code and paper.
Can you explain it more?
Thanks for your help.
At the moment, the dedicated strategy for verifying the results of quantum circuit compilation flows inherently assumes that the target gate set is IBM's modern gate-set (CX, ID, RZ, SX, X
), i.e., the profiles generated by https://qcec.readthedocs.io/en/latest/library/VerifyCompilation.html#compilation-flow-profile-generation only work reliably well for circuits compiled to that gate-set.
It would be great to make the gate-set configurable and offer more options.
Interface-wise, the verify_compilation
method would just receive an additional parameter that defaults to the new IBM gate-set and can optionally be set to a different gate-set.
Most certainly, it will make sense to introduce a Python Enum that specifies the available gate-sets.
This way, it can be ensured that pre-generated profiles for all combinations are available.
An alternative, that might require a little bit more work, would be to allow arbitrary gate sets (just as the transpile
method in Qiskit takes an arbitrary list of strings`). Whenever a pre-generated profile is available for the given gate-set, it can directly be used. When that is not the case, there are multiple options: either abort and prompt the user to generate the profile locally once, or automatically generate the profile on the first invocation (emitting a warning that this requires additional time; once).
At the very least, the old IBM gate-set ("id", "u1", "u2", "u3", "cx"
) should be added to the list of available options.
Probably it would also make sense to include the Rigetti gate set ("rx", "rz", "cz", "measure"
).
Overall it should be easy to adapt the profile generation code to additionally take a gate-set as a parameter that is being passed through to the transpile
call.
For the given circuit, equivalence verification crashed for the transpiled circuit. The error log is [1] 3989380 IOT instruction (core dumped) python bug.py
.
After mapping physical qubits back to logical qubits, the verification could run, but gave False
, which was unexpected.
No response
from qiskit import QuantumCircuit
from qiskit import transpile
from qiskit import QuantumCircuit
from qiskit.providers.fake_provider import FakeAuckland
def num_gates(qc: QuantumCircuit) -> int:
return sum(qc.count_ops().values())
def check_qcec(qc_x: QuantumCircuit, qc_y: QuantumCircuit) -> bool:
from mqt import qcec
result = qcec.verify(qc_x, qc_y)
# ic(result.equivalence)
return result.considered_equivalent()
backend = FakeAuckland()
qc = QuantumCircuit.from_qasm_str('''OPENQASM 2.0;
include "qelib1.inc";
qreg q[5];
h q[4];
cx q[3],q[4];
tdg q[4];
cx q[2],q[4];
t q[4];
cx q[3],q[4];
tdg q[4];
cx q[2],q[4];
cx q[2],q[3];
t q[4];
tdg q[3];
cx q[2],q[3];
t q[2];
t q[3];
h q[3];
cx q[1],q[3];
tdg q[3];''')
qc.draw('mpl', filename='test.png')
print(num_gates(qc))
qc_basis = transpile(qc, backend, optimization_level=3)
qc_basis.draw(output='mpl', filename='test_basis.png')
print(qc_basis.qasm())
'''
OPENQASM 2.0;
include "qelib1.inc";
qreg q[27];
rz(pi/2) q[13];
sx q[13];
rz(pi/2) q[13];
cx q[14],q[13];
rz(-pi/4) q[13];
cx q[12],q[13];
sx q[12];
rz(-pi) q[12];
rz(pi/4) q[13];
cx q[14],q[13];
rz(-pi/4) q[13];
sx q[13];
rz(-3*pi/2) q[13];
cx q[13],q[12];
rz(pi/2) q[12];
sx q[13];
cx q[13],q[12];
rz(-pi/4) q[12];
sx q[13];
rz(-pi/4) q[13];
cx q[13],q[14];
rz(-pi/4) q[14];
cx q[13],q[14];
rz(3*pi/4) q[14];
sx q[14];
rz(pi/2) q[14];
cx q[16],q[14];
rz(-pi/4) q[14];
'''
# print(qc_basis.layout.initial_layout.get_physical_bits())
print(num_gates(qc_basis))
# print(check_qcec(qc, qc_basis)) # CRASHED: [1] 3989380 IOT instruction (core dumped) python bug.py
'''map physical qubit in qc_basis back to logical qubit in qc
13 -> 4
14 -> 3
12 -> 2
16 -> 1
21 -> 0
'''
qc_match = QuantumCircuit.from_qasm_str('''OPENQASM 2.0;
include "qelib1.inc";
qreg q[27];
rz(pi/2) q[4];
sx q[4];
rz(pi/2) q[4];
cx q[3],q[4];
rz(-pi/4) q[4];
cx q[2],q[4];
sx q[2];
rz(-pi) q[2];
rz(pi/4) q[4];
cx q[3],q[4];
rz(-pi/4) q[4];
sx q[4];
rz(-3*pi/2) q[4];
cx q[4],q[2];
rz(pi/2) q[2];
sx q[4];
cx q[4],q[2];
rz(-pi/4) q[2];
sx q[4];
rz(-pi/4) q[4];
cx q[4],q[3];
rz(-pi/4) q[3];
cx q[4],q[3];
rz(3*pi/4) q[3];
sx q[3];
rz(pi/2) q[3];
cx q[1],q[3];
rz(-pi/4) q[3];''')
print(check_qcec(qc, qc_match)) # False???
At the moment, some preprocessing optimization passes fail when dealing with QuantumComputation
s involving SymbolicOperation
s. Many (if not all) of the optimization passes do not require the circuit to consist entirely of non-symbolic operations (like SWAP reconstruction).
When checking parameterized circuits for equivalence, optimization passes should be transparently applied just like with non-parameterized circuits. This requires minor adjustments in the respective optimization passes.
clang-tidy
version to v16
is available in CLion, but we still use clang-tidy-14
.
Make sure clang-16
is available in CI and switch back to the cpp-linter
GitHub action from the pure Python package and enables the new "step-summary" feature.
Reference PR:
C++ builds under Linux can be made faster with the mold
linker.
Use the mold
linker as the default linker for Linux CI builds.
Reference PR:
The QCEC project already supports a wide range of systems and provides wheels for essentially all of them.
There are two architectures out there, that are not yet supported with pre-built wheels
ppc64le
: PowerPC systemss390x
: Linux on IBM Z systemsFor both of these, emulation is required, since no native runners are available. The most commonly used tool for that is QEMU and it can neatly be integrated into CI. The bad thing about emulation is that it takes orders of magnitude longer than regular jobs.
It would be great for QCEC to also support these two platforms so it can be effortlessly used alongside Qiskit.
Two examples of Python packages offering support for both platforms are Qiskit and tweedledum, see https://github.com/Qiskit/qiskit-terra/blob/b7d5f4af39a37982d9d5637dd786c7f1f74132e6/.github/workflows/wheels.yml#L8-L77 and https://github.com/boschmitt/tweedledum/blob/a4549579873b69466d32600b6a7f2e68f5486aee/.github/workflows/build_wheels.yml#L93-L128.
It's probably best to start from there and do a basic setup. Since GitHub has a 6h time limit on individual jobs, the generation of these wheels should most likely be spread out in a build matrix so that each build is a separate job.
The big question is: are we actually willing to perform these time-consuming jobs on every commit?
Or do we risk it and just run those on releases and hope for the best.
I know that some repositories run a certain set of tests automatically and only after a certain trigger (some kind of approval on GitHub by a maintainer), the rest of the jobs get started. I am not too familiar with how to set something like this up, but it could be a great solution. Maybe starting the linter and test jobs and some of the "easy" Python packaging jobs automatically and then triggering the full test suite on demand.
Each simulation is executed on a TaskManager. However, after completing each simulation, the TaskManager did not reset the iterator to the beginning of quantum operations so that the next simulation could be executed normally.
Therefore, the iterator remains at the end of quantum operations and nothing is executed for the next simulation. Consequently, the result from the second simulation is always equivalent.
After completing each simulation, the TaskManager has to reset the iterator to the beginning of quantum operations.
See the situation by simply checking the equivalence of the following two circuits with simulations
qc1.x(0);
qc2.x(0);
// add a global phase of -1
qc2.z(0);
qc2.x(0);
qc2.z(0);
qc2.x(0);
FetchContent_Declare(mqt-core
GIT_REPOSITORY https://github.com/cda-tum/mqt-core
GIT_TAG 1.11.1
FIND_PACKAGE_ARGS REQUIRED
)
This allows the user to switch between:
-DFETCHCONTENT_TRY_FIND_PACKAGE_MODE=NEVER
REQUIRED
if you wish to have it fallback-DFETCHCONTENT_SOURCE_DIR_<uppercaseName>=/path/to/submodule
Find<Package>.cmake
Note that the full syntax is only available in Cmake 3.24
, but you can make a simple compatibility: example
Please link upstream to this issue as well
HI! Thank you for developing this tool.
I was trying to run benchmarks with more than 128 qubits, and then the following message was shown by the program:
what(): Requested too many qubits to be handled by the DD package. Qubit datatype only allows up to 128 qubits, while 232 were requested. If you want to use more than 128 qubits, you have to recompile the package with a wider Qubit type in export/dd_package/include/dd/Definitions.hpp!
Thus, I changed the qubit type in Definitions.hpp from std::int_fast8_t to std::int_fast16_t and recompiled the package. However, the compilation was not successful and the following error message was provided:
qcec/extern/qfr/src/algorithms/Grover.cpp:41:69: error: no matching function for call to βmax(long unsigned int, int)β
41 | auto target = static_castdd::Qubit(std::max(nqubits - 1, 0));
Using other types does not work either. Am I missing something out?
Thank you!
Any OS or Python version. Dates back to the very beginning of QCEC.
If two circuits work on the same number of qubits and one of them has an idle qubit (a qubit that does not have a gate acting on it), the equivalence check might fail because the output permutations of the circuits might not match after the idle qubit in one circuit has been stripped.
This behavior is a result of the fact that the circuits are stripped of idle qubits in isolation, i.e., without considering the other circuit.
mqt-qcec/src/EquivalenceCheckingManager.cpp
Lines 210 to 212 in 5d9cbc1
An idle qubit should only be removed if it either
This requires a dedicated stripIdleQubits
method in QCEC that considers both circuits in conjunction.
A corresponding implementation can be heavily inspired by the original mqt-core
implementation.
However, I believe that the overall logic could even be improved a little bit given the constrained setting here in QCEC.
TEST_F(EqualityTest, NotEqualBecauseOfIdleQubitStripping) {
// we measure only the second qubit
qc1 = qc::QuantumComputation(2, 1);
qc1.h(0);
qc1.x(1);
qc1.measure(1, 0);
qc1.setLogicalQubitGarbage(0);
// the first qubit doesn't have gates here, so stripIdleQubits() will remove
// it and change the output permutation accordingliy
qc2 = qc::QuantumComputation(2, 1);
qc2.x(1);
qc2.measure(1, 0);
qc2.setLogicalQubitGarbage(0);
// run the construction checker -> it will result in `NotEquivalent` because
// the two circuits have a different output permutation after `stripIdleQubits()`
config.execution.runConstructionChecker = true;
ec::EquivalenceCheckingManager ecm(qc1, qc2, config);
ecm.run();
EXPECT_EQ(ecm.equivalence(), ec::EquivalenceCriterion::NotEquivalent);
}
Thanks @reb-ddm for bringing this up!
CI builds can be quite slow and take up a considerable amount of time.
ccache
, in particular https://github.com/hendrikmuhs/ccache-action allows to set up a consistent compiler cache for your GitHub CI workflows that speeds up compilation across runs.
Adopt ccache
for our CI builds. Should be as simple as
- name: Setup ccache
uses: hendrikmuhs/[email protected]
with:
key: '${{matrix.os}}-${{matrix.compiler}}-${{matrix.build_type}}'
variant: ccache
save: true
max-size: 10G
and passing -D CMAKE_C_COMPILER_LAUNCHER=ccache -D CMAKE_CXX_COMPILER_LAUNCHER=ccache
to CMake.
Some slight modification might be necessary for Windows.
Reference PR:
latest
any
any
any
No response
The ZX checker seems to stop the overall equivalence checking run if it concludes that it cannot prove the equivalence of the circuits in question by providing the following message
Finished equivalence check provides no information. Something probably went wrong. Exiting.
The ZX checker should not interrupt the overall equivalence checking flow if it cannot conclude equivalence.
Run
from mqt import qcec
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import FakeAthens
circ = QuantumCircuit(3)
circ.ccx(control_qubit1=0, control_qubit2=1, target_qubit=2)
circTransp = transpile(circ, backend=FakeAthens(), optimization_level=1)
print(qcec.verify(circ, circTransp).equivalence)
a couple of times (non-deterministic due to parallelism and machine specs) and get
Finished equivalence check provides no information. Something probably went wrong. Exiting.
{
"check_time": 0.00046442,
"equivalence": "no_information",
"parameterized": {
"performed_instantiations": 0
},
"preprocessing_time": 0.000158936,
"simulations": {
"performed": 0,
"started": 8
}
}
Running
from mqt import qcec
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import FakeAthens
circ = QuantumCircuit(3)
circ.ccx(control_qubit1=0, control_qubit2=1, target_qubit=2)
circTransp = transpile(circ, backend=FakeAthens(), optimization_level=1)
print(qcec.verify(circ, circTransp, run_zx_checker=True, run_alternating_checker=False, run_simulation_checker=False).equivalence)
shows that it is the ZX checker yielding no_information
.
Below is an example with qiskit QFT demonstrating an inability to verify via jkq.qcec with default config. Also shown is a successful verification via qiskit unitary_simulator and np.allclose.
Am I missing something in the config?
My env:
jkq.qcec 1.8.0
qiskit 0.24.0
qiskit-aer 0.7.6
qiskit-aqua 0.8.2
qiskit-ibmq-provider 0.12.1
qiskit-ignis 0.5.2
qiskit-terra 0.16.4
import networkx as nx
import matplotlib.pyplot as plt
def nngrid(nqbits_x, nqbits_y ):
G=nx.grid_2d_graph(nqbits_x,nqbits_y)
pos = nx.spring_layout(G, iterations=100)
plt.subplot(221)
nx.draw(G, pos, font_size=8)
plt.show()
cmap = []
for edge in G.edges():
cmap.append([ edge[0][0] + edge[0][1]*nqbits_x ,
edge[1][0] + edge[1][1]*nqbits_x ])
return cmap
from jkq.qcec import Configuration, Strategy, verify
from qiskit import QuantumCircuit, transpile
basis_gates = ['h', 'cp','swap']
opt_level=0
cmap=nngrid(2,2)
qft = QFT(nqbits)
qc_tran = transpile(qft,optimization_level=opt_level,
basis_gates=basis_gates,
coupling_map=cmap)
config = Configuration()
#config.strategy = Strategy.compilationflow
#Strategy.naive
#Strategy.proportional
#config.strategy = Strategy.lookahead
#config.tolerance = 1e-8
result = verify(qft, qc_tran, config)
print(result)
simulator = Aer.get_backend("unitary_simulator")
result_qft = execute(qft, simulator).result()
unitary_qft = result_qft.get_unitary(qft)
result_tran = execute(qc_tran, simulator).result()
unitary_tran = result_qft.get_unitary(qc_tran)
np.allclose(unitary_qft, unitary_tran)
At the moment, the public verify
and verify_compilation
methods per default automatically orchestrate a combination of all available equivalence checkers in order to provide an automated black-box solution.
However, for academic purposes and reproducibility, it might be great to only run a single equivalence checker.
While this is possible right now by appropriately setting the run_*_checker
configuration options, this might not be intuitive to users and is not very well reflected in the documentation.
In order to provide more transparency and better usability, it should be possible to select an individual method to be run by setting a single option. In the simplest case, this amounts to creating a new Enum Method
which might feature the following values for now:
auto
: the default as used now. orchestrates all different checkers.alternating
: the alternating checkerconstruction
: the construction checkersimulation
: only run simulationszx
: the ZX checkerRegarding the interface, the Configuration:: Execution
struct can take an additional method
member and the verify functions take an additional keyword argument method
.
Internally, this merely overrides the run_*_checker
configuration settings.
As a result, the documentation of the individual checkers can be extended with a simple example of how to use them.
See the conversation at #167 (comment)
latest (dates back to earlier versions)
any (bug surfaced on Windows in Debug mode)
No response
No response
No response
While working on improving our CI, the following error has popped up:
https://github.com/cda-tum/mqt-qcec/actions/runs/5956825148/job/16158452626?pr=301#step:8:212
[ RUN ] ZXTest.IdleQubit
C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.35.32215\include\vector(3263) : Assertion failed: vector subscript out of range
Which indicated that some vector is being accessed beyond its bounds. The failure can be traced down to the following method:
mqt-qcec/src/checker/zx/ZXChecker.cpp
Lines 121 to 147 in 1e3f721
and the problematic line that causes the out-of-bounds access is
mqt-qcec/src/checker/zx/ZXChecker.cpp
Line 127 in 1e3f721
In the IdleQubit test
Lines 380 to 411 in 1e3f721
q[3]
) that is removed from the circuit as part of the ECM creation. This leaves the circuit with qubits 0, 1, 2, 4
. Now, somewhere along the process, this gap in the number of qubits comes in and the problematic line above is called with k==4
and v==3
.
No out-of-bounds access should ever happen
Run the IdleQubit
test from the ZXTest
suite in Debug mode (easiest under Windows because that explicitly adds bounds checks). Otherwise, work with debug prints, etc.
mqt.qcec>=2.0.0
Ubuntu 20.04.5 LTS
3.8.10
g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
No response
The ZX Checker yields incorrect results in certain cases, e.g., the following circuits
OPENQASM 2.0;
include "qelib1.inc";
qreg qubits[1];
and
OPENQASM 2.0;
include "qelib1.inc";
qreg qubits[1];
h qubits[0];
are erroneously considered equivalent.
Circuits should be detected as non-equivalent.
from mqt import qcec
from qiskit import QuantumCircuit
qc1 = QuantumCircuit(1)
qc2 = QuantumCircuit(1)
qc2.h(0)
result = qcec.verify(qc1, qc2, run_alternating_checker=False, run_simulation_checker=False)
print(result.equivalence)
At the moment, the most relaxed kind of equivalence between circuits is equivalence up to global phase.
In https://arxiv.org/abs/2208.07564 and https://arxiv.org/abs/2106.01658 more relaxed versions of equivalence are considered. Particularly, the idea is introduced that two circuits can be considered equivalent if they produce the same measurement probabilities for qubits being measured.
It should be rather straight-forward to incorporate such a check into the DD-based equivalence checkers. Essentially, this should boil down to checking whether the DD resulting from the alternating method resembles a diagonal matrix.
What needs to be added is a new Enum value (something like EquivalentMeasurementOutcome
) and the corresponding functionality in the equality check of the DD checkers.
QCEC can be used to verify the results of compilation flows, as detailed in the documentation: https://qcec.readthedocs.io/en/latest/CompilationFlowVerification.html
An essential part for the verification to work properly is that QCEC has to be able to infer information on the initial layout and the output permutation of the qubits, i.e., it has to know which physical qubits of a device the logical qubits of a circuit have been assigned to (initial layout), and where each logical qubit ended up at the end of the computation (output permutation).
To this end, the initial layout is inferred from a _layout
member that Qiskit adds to circuits that have been transpiled (with the transpile
function).
At the moment, the only way to infer the output permutation is from the measurements at the end of both circuits.
The transpile
function of Qiskit only tracks this information, if the original circuit being transpiled contains measurements. Otherwise there is no chance to properly infer the expected output permutation of a compiled circuit.
Thus, it is paramount, that users trying to verify compilation flow results append measurements to their original circuit before transpilation. In order to avoid frustrating situations in the future, where users try to verify circuits and quickly find that QCEC reports their circuits as non-equivalent (although they carefully checked that they are equivalent), a warning should be emitted if any of the circuits passed to the verify_compilation
function does not contain measurements. The corresponding check should probably be placed somewhere here:
Furthermore, this requirement should be featured more prominently in the documentation. Probably here:
https://github.com/cda-tum/qcec/blob/main/docs/source/CompilationFlowVerification.rst#using-qcec-to-verify-compilation-flow-results
This should be a pretty easy addition to the library with a potentially great impact. If you'd like to work on this, feel free to submit a PR.
Currently, there is a bug in the underlying matrix representation of the RZ gate (see cda-tum/dd_package#25) that just recently popped up due to IBM switching to their new [rz, sx, cx]
gate set. As of now, circuits imported from Qiskit that contain rz
gates do not work properly.
A fix is in the works and a new version will be released soon.
mqt.qcec===2.0.0
All
=3.7
No response
No response
The timeout configuration in the verify
and verify_compilation
method has to be either passed as a datetime.timedelta
object or a float
. It is a bit unintuitive that timeout=3600
gives an error.
No response
Call verify(qc1, qc2, timeout=1)
with any two QuantumCircuit
objects.
latest
any
any
any
No response
The ZX checker seems to have some problems when a circuit contains idle qubits that are stripped away during pre-processing in the EquivalenceCheckingManager. The problem causes it to segfault during the construction of the ZX miter. A minimal reproducer is given below.
It seems that the problem occurs in those cases, where the idle qubit is in-between non-idle qubits.
The ZXChecker should be able to handle circuits where idle qubits have been stripped.
run
auto qc1 = qc::QuantumComputation(1U);
qc1.h(0);
qc1.measure(0, 0);
auto qc2 = qc::QuantumComputation(3U);
qc2.h(2);
qc2.measure(2, 0);
qc2.initializeIOMapping();
auto config = ec::Configuration{};
config.execution.runZXChecker = true;
config.execution.parallel = false;
config.execution.runSimulationChecker = false;
config.execution.runAlternatingChecker = false;
config.execution.runConstructionChecker = false;
ec::EquivalenceCheckingManager ecm(qc1, qc2, config);
ecm.run();
Due to flake8
not having native support for configuration via pyproject.toml
, this project uses pyproject-flake8
(https://github.com/csachs/pyproject-flake8) which monkeypatches flake8
to add that support.
As of the latest flake8
release (5.0.0), this is broken in a way that is non-trivial to fix (see csachs/pyproject-flake8#13). Due to the unwillingness of the maintainers of flake8 to adopt pyproject.toml
support (PyCQA/flake8#234) anytime soon, this frequently breaks the pre-commit update.
One possible solution to fix these breakages is to move the flake8 configuration from the pyproject.toml
to a separate .flake8
file.
Hi,
It seems that two circuits which are different by a diagonal gate just before the measurements will be interpreted as non-equivalent by QCEC checker? Say, two circuits like:
qc1:
X q[0];
measure q[0] -> c[0];
and qc2:
X q[0];
Z q[0];
measure q[0] -> c[0];
The two circuits above implement different unitaries, however the measurement results will be the same.
Is it possible to implement such a functionality (equivalence checker in the presence of diagonal gates before measure gates)?
E.g. Qiskit has a RemoveDiagnoalBeforeMeasure subroutine as a part of transpiler.
While the verify functions are convenient to use as compared to explicitly constructing an EquivalenceCheckingManager and running it, they only return a limited amount of results/statistics. For example,
{
"check_time": 0.000280903,
"equivalence": "equivalent",
"preprocessing_time": 0.000150126,
"simulations": {
"performed": 0,
"started": 4
}
}
While this is completely sufficient from an end-user point of view, it might make it quite a bit harder to get statistics such as circuit metrics, configurations, or more detailed results on the underlying equivalence checking process. These results are actually available in the EquivalenceCheckingManager
. However, they are not transparently exposed through the public verify
interface at the moment.
It would be great to provide access to
via the verify
function interface. Most likely, it is easiest to slightly refactor the QCEC internals by augmenting the Results class and moving some of the tracked statistics there.
Following the mantra that it is always easier to filter available data than it is to re-run computations with statistics enabled, these additional statistics should be available by default.
It might make sense to provide additional methods for filtering in the Results
class.
Hi,
I was wondering if it is possible to add a Python interface for your project?
That would be very helpful for the platform I'm working on (Arline Benchmarks, automated benchmarking for quantum compilers, https://github.com/ArlineQ/arline_benchmarks).
Also it would be super helpful for a broad audience of quantum researchers and developers.
Me and my team looked into that, there are some automatic python wrappers for C++ code such as pybind11, SWIG.
Did you guys consider something like that?
Thanks!
latest
any
any
any
No response
QCEC might unexpectedly segfault when circuits to not contain measurements (that allow to infer the output permutation).
The observed exception is
libc++abi: terminating with uncaught exception of type std::invalid_argument: Alternating checker must not be used for circuits that both have non-idle ancillary qubits. Use the construction checker instead.
However, the circuits in question do not contain non-idle ancillaries, which indicates that one of the checks does not fully support the absence of measurements. Should be fairly easy to fix.
QCEC should not segfault on circuits compiled without measurements at the end of the circuit.
Run
from mqt import qcec
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import FakeAthens
circ = QuantumCircuit(1)
circ.x(0)
circTransp = transpile(circ, backend=FakeAthens())
result = qcec.verify(circ,circTransp)
The current CI setup is slower than it needs to be due to the C++ coverage job depending on the standard C++ build.
These can be decoupled to improve the CI runtimes.
Reference PR:
At the moment, we provide Apple Silicon support through cross-compilation. While this is great and allows people on M1/M2 Macs to natively use our tools, cross-compilation also implies that the produced wheels cannot be tested.
It would be great to switch to native runners for building these wheels in order to also ensure that they work properly.
While not a big problem in QCEC, eliminating cross-compilation also eliminates the need to care for native wheels of dependencies being available.
Native Apple Silicon runners are starting to become available. Cirrus CI offers free CI for public repositories.
At the very least, it provides Apple Silicon runners -- effectively making the cross-compilation job obsolete.
In addition, they offer native linux arm64
virtual machines, which would constitute another platform to the (already long) list of supported systems.
Cirrus CI is already set up for our GitHub organisation. Enabling it should be as easy as adding an appropriate configuration file as detailed in their documentation. It might help to check out the configuration of cibuildwheel (https://github.com/pypa/cibuildwheel/blob/main/.cirrus.yml).
latest
any
No response
No response
No response
The tool that is being used for linting the C++ part of our codebase (cpp-linter) recently got a major update (https://github.com/cpp-linter/cpp-linter-action/releases/tag/v1.5.0) a couple of days ago. That release partially fixed cpp-linter/cpp-linter-action#73, which QCEC was working around in https://github.com/cda-tum/qcec/blob/2d2ea8253074397cc3afdddfb93d6d4de0fb6042/.github/workflows/cpp-linter.yml#L41.
This workaround now silently broke with the most recent update.
This hasn't been caught by the latest PRs since they did not contain C++ related changes, but popped up when we tried to introduce the action for the MQT DD Package in cda-tum/dd_package#103.
The linter should work as expected.
Run the cpp-linter action or Python package with these two lines
https://github.com/cda-tum/qcec/blob/2d2ea8253074397cc3afdddfb93d6d4de0fb6042/.github/workflows/cpp-linter.yml#L37-L38
set to false.
At the moment, an EquivalenceCheckingManager
in Python is either constructed from a Configuration
object or from kwargs
. But when calling verify
or verify_compilation
with a mixture of kwargs
and a Configuration
the equivalence checking manager is only constructed from the kwargs
and the Configuration
is ignored.
It would be ideal to put construction of EquivalenceCheckingManager
objects entirely in the hand of Configuration
objects.
That would require making Configuration
constructable from kwargs
. Furthermore a update_config
method that updates the entries of a Configuration
object with a dict
would be great to make updating a Configuration
object from kwargs
as easy as possible.
Hi,
In the include/EquivalenceChecker.hpp, the variable end2 is set to qc1.cend().
Is it a mistyping?
I think end2 should be set to qc2.cend().
qiskit<0.45
resolves the problem)While looking through the example docs, I noticed that the very simple example shown in the documentation for verifying compilation flows is broken as it reports not_equivalent
and emits a warning that both circuits do not operate on the same number of qubits.
After checking the example locally, it turns out that this is a regression in the latest qiskit version. Downgrading qiskit solves the problem. Two things that could be at fault here are the new Singleton Gates or some changes in the _layout
attribute.
Circuits should be considered equivalent (as they always have been).
from qiskit import QuantumCircuit, transpile
from qiskit.providers.fake_provider import FakeLondon
from mqt import qcec
circ = QuantumCircuit(3)
circ.x(2)
circ.h(range(3))
circ.ccx(0, 1, 2)
circ.h(range(2))
circ.x(range(2))
circ.h(1)
circ.cx(0, 1)
circ.h(1)
circ.x(range(2))
circ.h(range(2))
circ.measure_all()
optimization_level = 1
circ_comp = transpile(circ, backend=FakeLondon(), optimization_level=optimization_level)
print(qcec.verify_compilation(circ, circ_comp, optimization_level=optimization_level))
The default (application) scheme determining in which sequence gates of both circuits are applied is the proportional
strategy.
The corresponding computation is compactly described by the following function
Essentially, it computes the (rounded) gate ratio between both circuits, e.g., if the first circuit contains 10
gates and the second circuit contains 40
gates, then the computed gate ratio would be 40 / 10 = 4
. This implies that for every gate applied from the first circuit four gates from the second circuit are applied.
In case of verifying the results of compilation flows, this is a reasonable strategy to account for the fact that high-level gates are synthesized to low-level basis gates---effectively increasing the circuit size proportionally.
That's all great. So why this issue? During the equivalence check, QCEC applies several optimizations to keep the check as efficient as possible. One of these optimizations is that SWAP operations are not actually applied to the state of the system but rather alter the qubit permutation that is being tracked throughout the check. This has been shown to improve the runtime of the equivalence check by some orders of magnitude. For the respective place in the code, see:
As a result, SWAP operations should technically not be counted towards the gate counts of both circuits in order to properly reflect the application of operations. This should just be a simple change that, instead of just taking the size of the circuits, iterates over both circuits and counts the number of non-SWAP gates and computes the ratio from that.
This has the slight disadvantage of incurring an overhead linear in the number of gates of both circuits at construction time of the application scheme. On the upside it should provide a better default strategy for QCEC that allows to stay closer to the identity throughout the check.
If anyone wants to work on this small little project, feel free to get on it and submit a PR!
Hi,
great project! I was wondering if there is any way to perform equivalence checking in the presence of measure gates using your tool? (after my attempt I got an error message that measure gates are not supported yet).
In fact, most quantum compilation frameworks (e.g. Qiskit, Pytket) quite often perform relabelling of classical registers at the final measurement stage, e.g.
measure q[0] c[1]
measure q[1] c[0].
As a result the circuits are formally not equivalent, but if you would perform measurements and account for relabelling of classical registers so that the measurement results are correct. This trick allows compilers to avoid additional cost of inserting SWAP gates, which would be needed to undo the qubit permutation occurred during the routing.
I would imagine that this should not be very hard to implement in your code?
Thanks!
In order for Python type-checking to work properly and thoroughly, mypy
(or other checkers) need to be able to infer certain type information about the underlying package. Since the QCEC project builds a binary C++ extension via Python bindings, the corresponding type information is only available when the Python package is installed (i.e., the C++ extension module is compiled).
This is cumbersome and, in general, unnecessary.
Static type-checkers can infer type information from so-called stubs. These merely describe the public interface of a library and provide corresponding type hints. See PEP 561 for how to distribute such type information packages.
It would be great to provide Python stubs for the QCEC library (especially the pyqcec
compiled C++ extension) in order to make better use of static type checkers without having to compile the project everytime.
For pybind11 modules, there is https://github.com/sizmailov/pybind11-stubgen, which could serve as a could starting point.
Mypy itself also offers something similar: https://mypy.readthedocs.io/en/stable/stubgen.html#automatic-stub-generation-stubgen
mqt-qcec version: 2.2.3
qiskit version: 0.25.1
C++ compiler: g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
OS: DISTRIB_DESCRIPTION="Ubuntu 22.04.3 LTS"
The following circuits, one static and the other dynamic, are supposed to be equivalent. But your equivalent check returned not_equivalent
which is wrong. Why is this so? I guess the problem could be from the relabelling of the circuits when your equivalent checker converts from dynamic to static circuits before checking for equivalence. Could you please fix this? Copying @glassnotes.
from mqt import qcec
from qiskit import QuantumCircuit
import os
quantinuum_circ = QuantumCircuit(5)
quantinuum_circ.cx(1,2)
quantinuum_circ.cx(0,3)
quantinuum_circ.cx(1,4)
quantinuum_circ.cx(2,4)
quantinuum_circ.cx(3,4)
quantinuum_circ.measure_all()
quantinuum_circ.draw('mpl')
from qiskit import QuantumCircuit
quantinuum_dynamic_circ = QuantumCircuit(3, 5)
quantinuum_dynamic_circ.cx(0,1)
quantinuum_dynamic_circ.cx(0,2)
quantinuum_dynamic_circ.measure(0, 1)
quantinuum_dynamic_circ.reset(0)
quantinuum_dynamic_circ.cx(1,2)
quantinuum_dynamic_circ.measure(1, 2)
quantinuum_dynamic_circ.reset(1)
quantinuum_dynamic_circ.cx(0,1)
quantinuum_dynamic_circ.measure(0, 0)
quantinuum_dynamic_circ.cx(1,2)
quantinuum_dynamic_circ.measure(1, 3)
quantinuum_dynamic_circ.measure(2, 4)
quantinuum_dynamic_circ.draw('mpl')
config = qcec.Configuration()
config.optimizations.transform_dynamic_circuit = True
result = qcec.verify(quantinuum_circ, quantinuum_dynamic_circ, configuration=config)
# print the result
print(result.equivalence)
Below are two bv circuits as written in qiskit qasm that are supposed to be equivalent but failed the test. Note that I couldn't attach them as qasm file hence, I pasted them here.
First static circuit:
OPENQASM 2.0;
include "qelib1.inc";
qreg q[9];
creg meas[9];
h q[0];
h q[1];
h q[2];
h q[3];
h q[4];
h q[5];
h q[6];
h q[7];
h q[8];
z q[8];
cx q[0],q[8];
cx q[1],q[8];
cx q[3],q[8];
cx q[5],q[8];
cx q[6],q[8];
cx q[7],q[8];
h q[0];
h q[1];
h q[2];
h q[3];
h q[4];
h q[5];
h q[6];
h q[7];
barrier q[0],q[1],q[2],q[3],q[4],q[5],q[6],q[7],q[8];
measure q[0] -> meas[0];
measure q[1] -> meas[1];
measure q[2] -> meas[2];
measure q[3] -> meas[3];
measure q[4] -> meas[4];
measure q[5] -> meas[5];
measure q[6] -> meas[6];
measure q[7] -> meas[7];
measure q[8] -> meas[8];
The second dynamic circuit equivalent:
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c6[9];
h q[0];
h q[0];
measure q[0] -> c6[2];
reset q[0];
h q[0];
h q[0];
measure q[0] -> c6[4];
reset q[0];
h q[0];
h q[1];
z q[1];
cx q[0],q[1];
h q[0];
measure q[0] -> c6[0];
reset q[0];
h q[0];
cx q[0],q[1];
h q[0];
measure q[0] -> c6[1];
reset q[0];
h q[0];
cx q[0],q[1];
h q[0];
measure q[0] -> c6[3];
reset q[0];
h q[0];
cx q[0],q[1];
h q[0];
measure q[0] -> c6[5];
reset q[0];
h q[0];
cx q[0],q[1];
h q[0];
measure q[0] -> c6[6];
reset q[0];
h q[0];
cx q[0],q[1];
measure q[1] -> c6[8];
h q[0];
measure q[0] -> c6[7];
No response
from mqt import qcec
from qiskit import QuantumCircuit
import os
config = qcec.Configuration()
config.optimizations.transform_dynamic_circuit = True
result = qcec.verify(quantinuum_circ, quantinuum_dynamic_circ, configuration=config)
I have included other qasm files to reproduce the error for dynamic circuits equivalence checking. Here is the result I got:
mqt.qcec>=2.0.0rc7 (possibly earlier and probably related to the addition of the ZXChecker)
CI environment (mostly macOS)
No response
No response
No response
During regular testing as part of the continuous integration, sometimes CI gets stuck and runs into the 6h timeout of GitHub (see, e.g., https://github.com/cda-tum/qcec/runs/8076757049?check_suite_focus=true or https://github.com/cda-tum/qcec/actions/runs/3065358584/attempts/1).
This happens sporadically and for jobs that typically take a couple of milliseconds.
Two possible causes:
QCEC should not timeout for jobs that typically take less than a second.
At the moment, this can not be reliably reproduced.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.