GithubHelp home page GithubHelp logo

cda-tum / mqt-qcec Goto Github PK

View Code? Open in Web Editor NEW
85.0 6.0 20.0 3.14 MB

MQT QCEC - A tool for Quantum Circuit Equivalence Checking

Home Page: https://mqt.readthedocs.io/projects/qcec

License: MIT License

CMake 2.06% C++ 78.76% Python 19.18%
quantum-circuits quantum-computing equivalence-checker decision-diagrams verification mqt tum jku zx-calculus python

mqt-qcec's People

Contributors

burgholzer avatar dependabot[bot] avatar hillmich avatar pehamtom avatar pre-commit-ci[bot] avatar reb-ddm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

mqt-qcec's Issues

✨ More Gate-Sets for Compilation Flow Profiles

What's the problem this feature will solve?

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.

Describe the solution you'd like

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.

πŸ› equivalence verification crashed for a transpiled circuit

Environment information

  • OS: Ubuntu 22.04.3 LTS
  • mqt.qcec 2.4.1

Description

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.

Expected behavior

No response

How to Reproduce

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???

✨ Adjust optimization passes for parameterized circuits

What's the problem this feature will solve?

At the moment, some preprocessing optimization passes fail when dealing with QuantumComputations involving SymbolicOperations. Many (if not all) of the optimization passes do not require the circuit to consist entirely of non-symbolic operations (like SWAP reconstruction).

Describe the solution you'd like

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.

πŸ‘·πŸΌ Support for Building `s390x` and `ppc64le` Wheels

What's the problem this feature will solve?

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 systems
  • s390x: Linux on IBM Z systems

For 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.

Describe the solution you'd like

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.

πŸ› Simulations only work correctly for the first run and check nothing from the second run

Environment information

  • OS: Mac
  • C++ compiler: Apple clang version 15.0.0
  • mqt.core version: newest

Description

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.

Expected behavior

After completing each simulation, the TaskManager has to reset the iterator to the beginning of quantum operations.

How to Reproduce

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);

Recommendation use `FetchContent` instead of git module directly

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:

  • Downloaded dependency -DFETCHCONTENT_TRY_FIND_PACKAGE_MODE=NEVER
  • System installed (default): remove REQUIRED if you wish to have it fallback
  • Git submodule -DFETCHCONTENT_SOURCE_DIR_<uppercaseName>=/path/to/submodule
  • Other implementations: 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

Unable to recompile with wider qubit type

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!

πŸ› Incorrect stripping of idle qubits that are not idle in both circuits

Environment information

Any OS or Python version. Dates back to the very beginning of QCEC.

Description

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.

// strip away qubits that are not acted upon
this->qc1.stripIdleQubits();
this->qc2.stripIdleQubits();

Expected behavior

An idle qubit should only be removed if it either

  • is idle in both circuits
  • is idle in one and does not exist in the other circuit

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.

How to Reproduce

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!

✨ Adopt `ccache` for faster CI builds

What's the problem this feature will solve?

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.

Describe the solution you'd like

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:

πŸ› ZX-Checker may stop equivalence checking process when it cannot show equivalence

mqt.qcec version

latest

OS

any

Python version

any

C++ compiler

any

Additional environment information

No response

Description

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.

Expected behavior

The ZX checker should not interrupt the overall equivalence checking flow if it cannot conclude equivalence.

How to Reproduce

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.

Inability to verify qiskit QFT

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)

set dedicated verification strategy

config = Configuration()
#config.strategy = Strategy.compilationflow
#Strategy.naive
#Strategy.proportional
#config.strategy = Strategy.lookahead
#config.tolerance = 1e-8

verify the compilation result

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)

image

🚸 Run individual equivalence checkers

What's the problem this feature will solve?

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.

Describe the solution you'd like

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 checker
  • construction: the construction checker
  • simulation: only run simulations
  • zx: the ZX checker

Regarding 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.

Further details

See the conversation at #167 (comment)

πŸ› Subscript out of range in ZX Checker

mqt.qcec version

latest (dates back to earlier versions)

OS

any (bug surfaced on Windows in Debug mode)

Python version

No response

C++ compiler

No response

Additional environment information

No response

Description

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:

qc::Permutation complete(const qc::Permutation& p, const std::size_t n) {
qc::Permutation pComp = p;
std::vector<bool> mappedTo(n, false);
std::vector<bool> mappedFrom(n, false);
for (const auto [k, v] : p) {
mappedFrom[k] = true;
mappedTo[v] = true;
}
// Map qubits greedily
for (std::size_t i = 0; i < n; ++i) {
if (mappedFrom[i]) {
continue;
}
for (std::size_t j = 0; j < n; ++j) {
if (!mappedTo[j]) {
pComp[static_cast<qc::Qubit>(i)] = static_cast<qc::Qubit>(j);
mappedTo[j] = true;
mappedFrom[i] = true;
break;
}
}
}
return pComp;
}

and the problematic line that causes the out-of-bounds access is

mappedFrom[k] = true;

In the IdleQubit test

mqt-qcec/test/test_zx.cpp

Lines 380 to 411 in 1e3f721

TEST_F(ZXTest, IdleQubit) {
using namespace qc::literals;
auto qc1 = qc::QuantumComputation(3U);
qc1.h(0);
qc1.x(1, 0_pc);
qc1.x(2, 1_pc);
qc1.measure(0, 0);
qc1.measure(1, 1);
qc1.measure(2, 2);
qc1.initializeIOMapping();
auto qc2 = qc::QuantumComputation(5U);
qc2.h(1);
qc2.x(0, 1_pc);
qc2.swap(0, 2);
qc2.x(4, 2_pc);
qc2.measure(1, 0);
qc2.measure(2, 1);
qc2.measure(4, 2);
qc2.initializeIOMapping();
config.execution.runZXChecker = true;
config.execution.parallel = false;
config.execution.runSimulationChecker = false;
config.execution.runAlternatingChecker = false;
config.execution.runConstructionChecker = false;
ecm = std::make_unique<ec::EquivalenceCheckingManager>(qc1, qc2, config);
ecm->run();
EXPECT_EQ(ecm->getResults().equivalence,
ec::EquivalenceCriterion::NoInformation);
}

the second circuit has an idle qubit (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.

Expected behavior

No out-of-bounds access should ever happen

How to Reproduce

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.

πŸ› ZX-Checker produces invalid result

mqt.qcec version

mqt.qcec>=2.0.0

OS

Ubuntu 20.04.5 LTS

Python version

3.8.10

C++ compiler

g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0

Additional environment information

No response

Description

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.

Expected behavior

Circuits should be detected as non-equivalent.

How to Reproduce

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)

✨ Support for Equivalent Measurement Outcome

What's the problem this feature will solve?

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.

Describe the solution you'd like

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.

🚸 Missing Measurements Warning for `verify_compilation`

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:

https://github.com/cda-tum/qcec/blob/6f26b822cd1764d5acf9174dab037ddded2e6201/mqt/qcec/verify_compilation_flow.py#L16-L47

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.

Wrong results for Qiskit circuits containing RZ gates

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.

πŸ› Type error when specifying timeout as integer

mqt.qcec version

mqt.qcec===2.0.0

OS

All

Python version

=3.7

C++ compiler

No response

Additional environment information

No response

Description

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.

Expected behavior

No response

How to Reproduce

Call verify(qc1, qc2, timeout=1) with any two QuantumCircuit objects.

πŸ› Bug in ZXChecker permutation handling

mqt.qcec version

latest

OS

any

Python version

any

C++ compiler

any

Additional environment information

No response

Description

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.

Expected behavior

The ZXChecker should be able to handle circuits where idle qubits have been stripped.

How to Reproduce

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();

πŸͺ flake8 cannot be updated to `>=5.0.0`

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.

Circuit equivalence in the presence of diagonal gates before measure

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.

🚸 Better Result Reporting

What's the problem this feature will solve?

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.

Describe the solution you'd like

It would be great to provide access to

  • circuit statistics
  • configuration settings
  • detailed equivalence checking results

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.

Python interface

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!

πŸ› Circuits without measurements might cause segfaults

mqt.qcec version

latest

OS

any

Python version

any

C++ compiler

any

Additional environment information

No response

Description

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.

Expected behavior

QCEC should not segfault on circuits compiled without measurements at the end of the circuit.

How to Reproduce

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)

πŸ‘·πŸΌ Native Apple Silicon and Linux ARM Runners

What's the problem this feature will solve?

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.

Describe the solution you'd like

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).

πŸ› `cpp-linter` silently broke

mqt.qcec version

latest

OS

any

Python version

No response

C++ compiler

No response

Additional environment information

No response

Description

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.

Expected behavior

The linter should work as expected.

How to Reproduce

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.

✨ Move configuration of EquivalenceCheckingManager to the construction of the Config class

What's the problem this feature will solve?

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.

Describe the solution you'd like

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.

EquivalenceChecker.hpp end2

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().

πŸ› Regression using `qiskit>=0.45`.

Environment information

  • OS: any
  • Python version: any
  • mqt-qcec version: latest (but could be dating back a bit)
  • qiskit version: 0.45.0 (downgrading to qiskit<0.45 resolves the problem)

Description

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.

Expected behavior

Circuits should be considered equivalent (as they always have been).

How to Reproduce

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))

⚑ Improve Default Application Scheme

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

https://github.com/cda-tum/qcec/blob/6f26b822cd1764d5acf9174dab037ddded2e6201/include/checker/dd/applicationscheme/ProportionalApplicationScheme.hpp#L25-L30

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:

https://github.com/cda-tum/qcec/blob/6f26b822cd1764d5acf9174dab037ddded2e6201/include/checker/dd/TaskManager.hpp#L79-L84

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!

Measure gates and qubit relabelling

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!

✨ Provide stub files for Python type-checking

What's the problem this feature will solve?

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.

Describe the solution you'd like

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

Equivalence Checker for Dynamic Circuit failsπŸ›

Environment information

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"

Description

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];

Expected behavior

No response

How to Reproduce

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:

image

πŸ› Sporadic Timeouts

mqt.qcec version

mqt.qcec>=2.0.0rc7 (possibly earlier and probably related to the addition of the ZXChecker)

OS

CI environment (mostly macOS)

Python version

No response

C++ compiler

No response

Additional environment information

No response

Description

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:

  • Some threading issues leading to a thread not finishing and the main thread waiting on it
  • Some undefined behavior leading to endless loops (as previously observed in #102)

Expected behavior

QCEC should not timeout for jobs that typically take less than a second.

How to Reproduce

At the moment, this can not be reliably reproduced.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.