cda-tum / mqt-core Goto Github PK
View Code? Open in Web Editor NEWMQT Core - The Backbone of the Munich Quantum Toolkit
Home Page: http://mqt.readthedocs.io/projects/core
License: MIT License
MQT Core - The Backbone of the Munich Quantum Toolkit
Home Page: http://mqt.readthedocs.io/projects/core
License: MIT License
The new OpenQASM standard introduces many additional datatypes besides quantum and classical registers. This allows to describe classical components of quantum algorithms in a very intuitive way. As a first step, support for all kinds of new types should be added to the QFR library and, correspondingly, to the QuantumComputation class. For details on all the new typed provided by the standard, see https://qiskit.github.io/openqasm/language/classical.html.
Similar to #29, the QFR should support Integers, Floating point numbers, Fixed-point angles, complex numbers, and boolean types.
The QuantumComputation
class should be capable of allocating and maintaining these types. The ClassicalOperation
class introduced in #29, will most likely need to be expanded to incorporate all the possible operations on these classical types.
Depends on #29.
At the moment, the .real
file parser (see https://github.com/cda-tum/qfr/blob/main/src/parsers/RealParser.cpp) does not handle constant or garbage definitions properly.
This has not been particularly important up until now, since we typically just consider the whole state vector during simulation (e.g., in cda-tum/ddsim#main) and similarly the complete functionality for equivalence checking.
However, it might become important in the future for our endeavours towards reversible circuits.
In particular,
constant
should be properly flagged as an ancillary
qubit in the resulting QuantumComputation
.garbage
should be properly flagged as a garbage
qubit in the resulting QuantumComputation
.inputs
and outputs
could be used to determine the initial_layout
and the output_permutation
respectively. Although, it is not so clear whether this is possible for any .real
file.When reasoning about quantum circuits there are two degrees of freedom that can always lead to problems/confusion:
An arbitrary permutation of a circuit's qubits might be applied at the very beginning of the circuit (referred to as an "initial layout") or at the end of the circuit (referred to as an "output permutation").
In almost any case, a circuit containing such permutations would still be considered equivalent to a circuit without such permutations.
This is especially relevant for verifying the equivalence of quantum circuits and the results of compilation flows in particular.
For a long time, Qiskit has supported a _layout
property for its QuantumCircuit
objects that captured the initial layout applied during compilation. Within the MQT we use this property to infer the initialLayout
for our QuantumComputation
objects.
In 0.22.0
(which is now our minimum supported version), Qiskit changed the _layout
property to a proper TranspileLayout
class.
0.23.0
introduced an additional final_layout
component that (in some confusing fashion) captures the output permutation.
This should allow to also infer the output permutation from Qiskit.
Right now, we use the measurements in a circuit for that purpose, which is rathe prone to errors (e.g., someone not adding measurements to their circuit before compilation).
Starting with 0.45.0.
(which should be the next minor release), the ergonomics of the TranspileLayout
class have been considerably improved (see Qiskit/qiskit#10835).
That should yield a blueprint for how to interpret the final_layout
and to infer the outputPermutation
from Qiskit circuits.
A potential solution would first look if the final_layout
attribute is present.
If so, it would check whether the new convenient syntax (>=0.45.0) is available.
If not, it replicates the logic to parse the output permutation.
If no final layout is present, the solution should check for measurements in the circuit and infer the output permutation from there (as it currently does).
Might date back to rather old versions of the DD package. Still trying to figure that out.
The following simple example (that has been reduced from a much bigger real use case) consistently leaks decicion diagram nodes
#include "gtest/gtest.h"
#include "dd/Package.hpp"
using namespace qc;
TEST(DebugTest, debug) {
const auto nqubits = 1U;
auto matrix = dd::GateMatrix {
dd::complex_one,
dd::complex_zero,
dd::complex_zero,
dd::complex_zero
};
auto dd = std::make_unique<dd::Package<>>(nqubits);
auto dd1 = dd->makeGateDD(matrix, nqubits, 0U);
matrix[0] = dd::complex_zero;
matrix[3] = dd::complex_one;
auto dd2 = dd->makeGateDD(matrix, nqubits, 0U);
const auto repetitions = 10U;
for (auto i=0U; i<repetitions; ++i) {
dd->multiply(dd1, dd2);
}
dd->garbageCollect(true);
EXPECT_EQ(dd->mMemoryManager.getUsedCount(), 0U);
}
Since no reference counting is performed, every DD node should be reclaimed in the end.
However, with the current code base, every repetition of the main multiplication leaks a matrix DD node.
A simple example where computations as the above frequently arise is during the multiplication of CNOT gate DDs, e.g., CNOT(0, 1) * CNOT(0, 1)
.
After some painful digging, I found the error here:
mqt-core/include/dd/Package.hpp
Lines 603 to 611 in 4819bcd
The DD package should not leak memory.
Execute the above test and watch it fail.
In the current project configuration, link time optimization is enabled by default (to optimise performance).
This has lead to multiple cases where developers had issues during debugging, e.g., break points not being hit or code not being executed.
Consequently, IPO should be opt-in instead of opt-out.
Instead of enabling it by default, it can just be enabled in CI.
A recent commit changed the default behaviour of nlohmann/json to use JSON_MultipleHeaders=ON
instead of OFF
.
This breaks at least the build from sdist for DDSIM (which prunes the multiple header folder). This is locally fixable by setting the option accordingly (or use the multiple headers).
Question: Should we set JSON_MultipleHeaders=OFF
in the qfr via set(JSON_MultipleHeaders OFF CACHE INTERNAL "")
at the appropriate location?
Similar to our other repositories, a release drafter action should be set up here to ease the process of releasing new versions.
#280 has brought support for many new gates. The ZX library is yet to be updated to natively support these gates.
As such, circuits containing these operations cannot be translated to ZXDiagrams at the moment.
All that's required to fix this, is to provide the corresponding transformations in the ZX library.
The new OpenQASM standard introduces many additional datatypes besides quantum and classical registers. This allows to describe classical components of quantum algorithms in a very intuitive way. As a first step, support for all kinds of new types should be added to the QFR library and, correspondingly, to the QuantumComputation
class. For details on all the new typed provided by the standard, see https://qiskit.github.io/openqasm/language/classical.html.
Classical bits and registers could previously just be used as the target of a measurement and (in a very limited fashion) as the classical control in an if
statement. The new OpenQASM 3.0 standard enriches these capabilities by allowing to:
bit[8] a = "10001111";
&
), or |
, xor ^
and the corresponding assignment operators &=
, |=
, ^=
<<
and >>
, as well as <<=
and >>=
by an unsigned integer~
rotl
and rotr
First of all, the QuantumComputation
class needs to be capable of allocating and maintaining classical registers.
Most certainly, the manipulation of these registers will require the introduction of a new kind of operation, e.g., ClassicalOperation
.
Such an operation will not be unitary. As a consequence, the concept of the NonUnitaryOperation
as it is now may need to be revisited/refactored.
Depends on #28 for the availability of the bit
keyword in the parser.
API Docs are hard to get working with proper docstrings, automatically and look nice. Two out of the three are easy, but all three seem hard.
The solution implemented in #371 works automatically and looks nice (e.g., with working inter-doc and inter-sphinx links). However, it is purely generated from the type stubs, which do not contain docstrings.
As such, all the effort that went into providing docstrings in the bindings code is not reflected in the documentation.
IDEs do pick up the documentation from the bindings module and so do other API doc tools that inspect the compiled module. However, we haven't managed to get the rendered API docs to look nice and inter-doc linking to work properly.
The problem boils down to mqt.core
vs. mqt.core.core_
in the docstrings and how to make the docs only show the former and never refer to the latter.
One potential solution could be to have the docstrings in the stub files. However, this does not seem not be recommended as a ruff check removes them per default. That could be turned off though.
I also believe that IDEs do not pick up the documentation from stub files typically.
Having to duplicate the documentation (in bindings code and stubs) does not really make sense to me at all as it is not maintainable in the future.
It should be possible to set up automatically built API documentation for compiled projects that looks nice and contains all the docstrings so that IDEs and the Docs can pick them up. We are not the first project to do something like this, but I tried for quite some ours myself and what we have right now is the best I could come up with.
Any help is appreciated.
Running the following code
from qiskit import compiler, QuantumCircuit, QuantumRegister, ClassicalRegister, AncillaRegister
from jkq import qfr
flag = AncillaRegister(1, 'flag')
qc = QuantumCircuit(flag)
qc.x(flag)
qfr.construct(qc)
gives the following error message:
{'error': "Could not import circuit: KeyError: (AncillaQubit(AncillaRegister(1, 'flag'), 0),)"}
The OpenQASM 3.0 standard introduces a new type of gate gphase(γ)
that can be used to add a global phase e^iγ
to the scope containing the instruction.
This operation shall also be introduced as a new StandardOperation
.
Technically, this instruction has no target (since it is a global phase). As such, introducing it in the StandardOperations
class may require special treatment. Its decision diagram is given by the identity matrix where the top weight is changed to e^iγ
. The inverse is given by the identity with top edge weight e^-iγ
.
The new OpenQASM standard introduces many additional datatypes besides quantum and classical registers. This allows to describe classical components of quantum algorithms in a very intuitive way. As a first step, support for all kinds of new types should be added to the QFR library and, correspondingly, to the QuantumComputation class. For details on all the new typed provided by the standard, see https://qiskit.github.io/openqasm/language/classical.html.
It also introduces the concept of constant values that can be declared with the const
modifier. These are essentially known at "compile-time" and can be used to describe mathematical expressions (similar to OpenQASM 2.0's parameter expressions).
The QuantumComputation
class should be capable of managing constants and performing tasks such as constant propagation.
Depends on #30 for the availability of the new datatypes.
At the moment, each individual decision diagram operation has its own compute table. On the one hand, this is neat, since it allows to fine tune the sizes of the individual compute tables. On the other hand, it creates quite some complexity across the package. Reading the literature on BDD packages and looking at the history of this DD package, this is frequently handled differently: There is a single compute table and the operation being performed is factored into the hash during lookup.
For BDDs that is a no-brainer since there is only one type of operand so it is fairly efficient to cook up a compute table data structure for that. Typically, even unary operations are handled in the same table as binary operations.
In the very past, this was also possible (and implemented) for our DD package. This was due to there only being one type of node (vectors and matrices shared the same node type, but vectors only used two of the four successors).
However, in the presence of vector, matrix, and density matrix nodes, this is no longer as easy. There are many variations of triplets of the form (Operand1, Operand2, Result). It is unclear, how these could be combined in a feasible and efficient way. One possibility would be to resort to using variants for storing the individual types. But that would introduce a certain overhead in memory and runtime.
Why bother?
As always, this has to be tested and evaluated well in order to guarantee that the resulting solution indeed fulfills its goals.
Any version of the ZX library.
The arithmetic for PiExpressions in the ZX package seems to be quite buggy in certain aspects.
For example
const auto beta = zx::PiRational(1, 2);
std::cout << beta * 2 << "\n";
std::cout << beta * 2. << "\n";
const auto betaExpr = zx::PiExpression{beta};
std::cout << betaExpr * 2. << "\n";
// std::cout << betaExpr * 2 << "\n"; // does not compile due to missing overload
yields
1/1
1/1
159154943/500000000
The last entry is completely unexpected.
So that's the one thing. This also means something like betaExpr / 2.
will result in complete garbage.
Now, #482 adds support for divisions by integers (which allows to write the above as betaExpr / 2
).
However, that revealed quite a problem in the way arithmetic expressions are handled.
Namely, that
const auto beta = zx::PiRational(-1, 2);
std::cout << (beta + beta) / 2 << "\n"; // and, if the above is fixed most likely also `(beta+beta)/2.` which currently returns garbage
prints
1/2
instead of the desired result of -1/2
.
The reason for that is beta+beta
evaluating to -1
which is 1 mod pi
.
I would expect division and multiplication by floating point numbers for PiExpression
to work exactly as they do for PiRational
.
Furthermore, I would expect moderately complex arithmetic expressions to produce the correct result.
Run the examples from above
Nanobind (https://nanobind.readthedocs.io/en/latest/index.html) is a newer, faster and more lightweight alternative to pybind11
.
The preferred way to install it (according to the docs) is directly via pip
, which means fewer submodules for the project here.
There are a couple of things to take care of:
At the moment, the partial_trace
(and, hence, the trace
) function in the package are calculated in a straight-forward fashion. As a result, the performance of the operation scales with the number of paths in the decision diagram as opposed to the number of nodes. Introducing a compute table as for the other decision diagram operations fixes this problem. A possible benchmark to test this is to simply create the matrix of an n-qubit Hadamard transform and compute its trace. This matrix has n
nodes, but 4^n
paths.
Furthermore, it might be possible to implement an improved trace
function, that does not just call the partial trace and eliminates every variable, and instead just sums up all the diagonal entries.
It might even be beneficial to compute trace(...)/2^n
in order to normalise the result to the range [0, 1]
. Instead of computing trace(...)
and then dividing by 2^n
(which induces numerical problems), the computed trace can be divided by two in each of the levels in order to continuously normalise it throughout the computation.
The new OpenQASM standard introduces gate modifiers in order to more efficiently describe quantum circuits. Any modifier mod
can be applied to a gate g
via mod @ g
. For more details, see Section 4.2 in https://arxiv.org/pdf/2104.14722.pdf or the Live Specification.
The inversion modifier inv
can be used to invert any gate g
via inv @ g
. The inverse of any gate can be computed as:
U = U_m-1 ... U_0
its inverse U^-1
is defined by reversing the order of operations and inverting each individual gate, i.e., U^-1 = U_0^-1 ... U_m-1^-1
.inv @ ctrl @ g = ctrl @ inv @ g
.inv @ U(a, b, c) = U(-a, -c, -b)
and inv @ gphase(a) = gphase(-a)
Note that the global phase gate gphase(a)
is not yet introduced and its implementation is left for another issue at the moment.
We should be able to handle many of these inversions in a cleverer fashion, e.g., if it is known, that a gate is self-inverse, or e.g., in case of the phase gate, where the inverse is obtained by negating the parameter.
Enhancing the OpenQASM parser with this feature provides greater flexibility for describing circuits in a standardized way.
At the moment, the QFR library does not provide any means to access or alter quantum circuits from the Python side.
For some of our endeavours it might be beneficial to, at least, get read-only access to the QuantumComputation
class from Python.
This is closely tied with #35 since the Qiskit QuantumCircuit
object would most certainly also provide all the options we wish for and we already have the means to translate Qiskit's QuantumCircuit
class to our QuantumComputation
class.
It would be neat to add corresponding methods in the python
part of the codebase that allow to add corresponding bindings to any pybind11
module. That way, any top-level module can add these bindings to their Python modules without having to write everything from scratch.
The new OpenQASM standard introduces gate modifiers in order to more efficiently describe quantum circuits. Any modifier mod
can be applied to a gate g
via mod @ g
. For more details, see Section 4.2 in https://arxiv.org/pdf/2104.14722.pdf or the Live Specification.
One of these is the ctrl
modifier (and its sibling negctrl
) that allows to add a control to any already defined gate. The CX
gate, for example, is no longer a keyword in OpenQASM 3.0, but rather defined as:
gate cx c, t { ctrl @ x c, t; }
The negctrl
modifier allows to describe controls with a negative polarity.
Both modifiers optionally accept an argument n
which specifies the number of controls of the appropriate type, which are added to the front of the list of arguments. As a starter, n
can be assumed to be an integer. This, e.g., allows to realize a Toffoli gate as
gate ccx c0, c1, t { ctrl(2) @ x c0, c1, t; }
The standard only requires n
to be a "compile-time constant", which includes variables with a fixed value or iterator variables in for loops -- two concepts to be implemented separately.
Enhancing the OpenQASM parser with this feature should allow to simplify some of the complicated code that is used at the moment for handling controlled gates efficiently and provides greater flexibility for describing circuits in a standardized way.
Currently, there is no inverse gate type for iSwap
.
Add a new gate type iSwapDag
that represents the inverse of iSwap
.
At the moment, once garbage collection is called (and actually collects something), all compute tables are completely reset, i.e., emptied. While this is fast, it is not necessarily efficient. The compute table most probably contains many entries that are still valid (i.e., all constituents have a non-zero reference count).
The compute table reset should only clear the table from dead entries (i.e., where at least one of the edges has a zero reference count). In this fashion, valid entries are kept in the tables and can be used for further computations.
A prototypical implementation would be
void clear() {
if (count > 0) {
for (auto& entry: table) {
// If this is an unused entry, there is no need to clear it
if (entry.leftOperand.p == nullptr && entry.rightOperand.p == nullptr) {
continue;
}
// If all constituents of the entry have a non-zero reference count,
// the entry is still valid and should not be cleared
// This assumes that as long as a node is alive, the respective complex
// numbers are alive as well.
const auto leftAlive = entry.leftOperand.p == nullptr || entry.leftOperand.p->ref > 0;
const auto rightAlive = entry.rightOperand.p == nullptr || entry.rightOperand.p->ref > 0;
const auto resultAlive = entry.result.p == nullptr || entry.result.p->ref > 0;
if (leftAlive && rightAlive && resultAlive) {
continue;
}
entry = Entry{};
--count;
}
}
}
It's important to think about how often such a scenario might happen during multiplication and addition assuming the way we currently perform reference counting (e.g., the ref count of operation matrices is never increased).
At the moment, there are two memory managers for real numbers: one that manages the cache and one that provides entries for the respective unique table. Since both effectively provide the same type of entry, the question arises whether they can be unified to a single memory manager for numbers.
There are multiple sides to this:
According to https://openqasm.com/language/comments.html#version-string (emphasis is mine)
The first non-comment line of an OpenQASM program may optionally be OPENQASM M.m ; indicating a major version M and minor version m.
We should adapt our parser so that the version string is no longer mandatory.
The new OpenQASM standard introduces some new identifiers and relaxes some constraints on existing ones. In particular:
It is actually optional to specify the OPENQASM M.m
statement as the first line of a QASM file.
OpenQASM 2.0 used qreg
and creg
for declaring quantum and classical register. Although these are still supported by the new 3.0 standard, the preferred identifiers are now qubit
and bit
.
Furthermore, names of registers and gates are no longer required to begin with a lower-case ASCII character. Identifiers may now begin with other characters, such as capital letters, underscores, and a range of unicode characters. For example, angular values used as gate arguments may now be represented by greek letters.
The identifier π is reserved in OpenQASM 3 to represent the same constant as pi
Additional constants included in the OpenQASM 3 standard are tau
or τ
for the value 2*pi
and euler
or ℇ
for Euler's constant (2.7182818284...)
It also introduces a new syntax for expressing measurements. In addition to the existing syntax measure q[j] -> c[k]
, it also supports c[k] = measure q[j]
for measurements.
Last but not least, barrier
statements can appear without any target, which implies that a barrier should be applied targeting all qubits.
For more details, see https://arxiv.org/pdf/2104.14722.pdf or the Live Specification.
The ToffoliTable
data structure is used in the DD package to cache any decision diagrams for Toffoli gates that have been constructed.
This adds the need for special casing around Toffoli gates, introduces yet another lookup table that needs to be handled carefully, and hardly yields any performance benefits as the construction of Toffoli gates is linear in the number of qubits anyways.
Due to the above reason, the Toffoli Table should be removed in order to simplify the overall code.
In the future, it might be worth to investigate whether a general operation DD cache could be used to improve performance.
The OpenQASM 2.0 specification lacks a notion of global phase tracking. The OpenQASM 2.0 standard library contains the following definitions:
gate u1(lambda) q { U(0,0,lambda) q; }
gate p(lambda) q { U(0,0,lambda) q; }
gate rz(phi) a { u1(phi) a; }
This means that, the phase gate p
and the Z-rotation gate rz
boil down to the same gate. However, these two differ by a global phase factor, which is why the OpenQASM 3.0 spec defines these gates as
gate u1(lambda) q { U(0, 0, lambda) q; }
gate p(lambda) a { ctrl @ gphase(lambda) a; }
gate rz(lambda) a { gphase(-lambda/2); U(0, 0, lambda) a; }
Now, a global phase is not a problem per-se and can be ignored. But once controls are added, this phase becomes relative and plays a role, i.e., a cp
gate is different to a crz
gate. However, in our QASM parser, we simply strip away leading c
's, and look for a definition of the underlying gate. As a consequence, both gates (cp
and crz
) end up as cp
in the resulting quantum circuit.
I believe the underlying problem can be fixed by refactoring our parser a little bit, so that it does not look up definitions of gates we natively support in the QFR. That approach is also fairly close to what Qiskit is doing and should allow to reduce the complexity of some parts of the parser actually. Most likely, this requires adding further tokens to the parser or, alternatively , introducing some kind of dictionary of supported gates that can be used for mapping identifiers to (Standard)Operation
s or OpType
s with controls, targets and parameters.
Note: This is only a problem for the QASM import. The Qiskit QuantumCircuit import properly handles the distinction between phase and RZ gates.
The goal should be to setup a ReadTheDocs page similar to our top-level projects.
That page should at least cover all the installation and development steps.
Furthermore, it might take some inspiration from how @hillmich set up the C++ documentation in DDSIM a while back.
In the beginning, the C++ documentation will be pretty slim, as the code base is (unfortunately) rather badly documented.
The OpenQASM 3.0 standard provides the means to describe the flow of a program using if
statements, for
and while
loops.
This allows to more easily write static quantum algorithms that consist of iterations (such as Grover's algorithm or Phase Estimation) but also allows for the construction of sequential quantum circuits (such as Repeat Until Success schemes).
As a starting point, the creation of static algorithms should be supported using these constructs, i.e., programs should be supported that only contain constants and hence make it possible to unroll all loops at compile-time. An example of such a circuit is shown in https://arxiv.org/pdf/2104.14722.pdf
/*
* Iterative phase estimation
*/
OPENQASM 3;
include "stdgates.inc";
const n = 3; // number of iterations
const θ = 3*π/8; //phase angle on target qubit
qubit q; // phase estimation qubit
qubit r; // target qubit for the controlled-unitary gate
angle[n] c = 0; // phase estimation bits
// initialize
reset q;
reset r;
// prepare uniform superposition of eigenvectors of phase
h r;
// iterative phase estimation loop
for i in [1:n] { // implicitly cast val to int
reset q;
h q;
ctrl @ pow(2**i) @ phase(θ) q, r;
inv @ phase(c) q;
h q;
measure q -> c[0];
// newest measurement outcome is associated to a π/2 phase shift
// in the next iteration, so shift all bits of c left
c <<= 1;
}
// Now c contains the n-bit estimate of φ in the
// eigenvalue e^{i*φ} and qreg r is projected to an // approximate eigenstate of the phase gate.
In theory, this could be implemented independently from the other OpenQASM 3.0 features, but it probably makes sense to add support for constant values first (#31).
After #460, all gates in MQT Core have a proper inverse now.
A natural follow-up would be to consolidate the following methods:
mqt-core/src/operations/StandardOperation.cpp
Lines 471 to 559 in 95541a6
mqt-core/include/dd/Operations.hpp
Lines 27 to 93 in 95541a6
mqt-core/include/dd/Operations.hpp
Lines 117 to 237 in 95541a6
There is lots of redundancy in the handling of the inverse
throughout the code base.
I do not have a perfect solution for resolving that in mind. It would be pretty convenient though if any operation could return its gate matrix. So essentially to refactor https://github.com/cda-tum/mqt-core/blob/main/include/dd/GateMatrixDefinitions.hpp so that the appropriate matrix can be queried from an Operation
.
To this end, it could be nice to adopt a strategy similar to Qiskit where there are individual classes for gates that all derive from StandardOperation
(and potentially SymbolicOperation
for parametrized gates). As an example:
XGate
class that provides a method to get the single-qubit GateMatrix
, the type of the gate (already covered by StandardOperation), the inverse type, the inverse GateMatrix and potentially other useful methods.constexpr
convenience functions like isSingleTargetGate
, isTwoTargetGate
, and isThreeOrMoreTargetGate
would probably make sense.iSWAPdg
redundant again.Originally posted by @burgholzer in #460 (review)
When passing a file name containing more than 1 dot (e.g. test.circuit.qasm
) to QuantumComputation::dump
(or by extension any of its wrapper functions like QuantumComputation::dumpOpenQASM
), the assertion in line 716 of QuantumComputation.cpp
fails:
assert(std::count(filename.begin(), filename.end(), '.') == 1);
If I understand this line correctly, it tries to assert, that the supplied file name includes a file extension (though I'm not quite sure, why this would even be necessary in line 716, where the dumping format is already clear; maybe this line actually belongs before current line 622 const std::size_t dot = filename.find_last_of('.');
?).
In any case, if the only objective is to test for the existence of a file extension, shouldn't it be:
assert(std::count(filename.begin(), filename.end(), '.') >= 1);
I would expect to be able to pass any valid file name (or even file path) to QuantumComputation::dump containing a valid quantum circuit file extension but no other constraints (and in case I supply an explicit qc::Format
even the file extension to be irrelevant)
qc::QuantumComputation qc{4, 4};
qc.x(0, qc::Control{1});
qc.x(2, qc::Control{3});
qc.dump("test.circuit.qasm", qc::Format::OpenQASM);
// or
qc.dump("./test/circuit.qasm", qc::Format::OpenQASM); // second point not even in file name itself but only in parent path
Similar to our other projects, this project should include corresponding community health files.
Many of the DD routines throughout the package include a start
parameter that is supposed to indicate the lowest index in the DD. This was mainly introduced for the Hybrid Schrödinger-Feynman (HSF) simulator and isn't used anywhere else.
As such, it is kind of a nuisance and leads to a lot of special handling throughout some of the functions.
It should be possible to work around this requirement by refactoring parts of the HSF simulator in conjunction with the package code here.
Related upstream-stream issue:
Trying to take the benchmark suite from here and make it the benchmark suite of mqt-core.
Similar to our other repositories, there should be corresponding templates here.
Right now, the Qiskit import introduced in #371 incurs some unnecessary copies when translating operations from Qiskit to our internal representation.
This is because the code is organised so that it can simultaneously/recursively handle CompoundOperation
s and QuantumComputation
s. However, only the QuantumComputation
class has convenience methods for directly constructing gates in-place right now.
As a consequence, the Qiskit import just always constructs an operation and then moves it to the QuantumComputation or CompoundOperaton (incurring a copy).
It should be possible to avoid the copy and always construct the operations in-place (even if they are nested in a CompoundOperation).
Most likely, the solution is to replace the current call sites where a Compound operation is used by creating a QuantumComputation, then recursively calling the gate import on that circuit (using the convenience functions for adding gates in-place), and afterwards use the to_operation
method to turn the circuit into a CompoundOperation that can then be added to the circuit.
An alternative solution involves also exposing convenience functions for adding gates in-place to the CompoundOperation class similar to the QuantumComputation. However, this creates quite some overhead in documentation and in terms of lines of code and is, therefore, not the preferred solution.
At the moment, the maximum number of qubits for quantum circuits is limited by the maximum number allowed by the underlying decision diagram package (128
per default). This is an unnecessary restriction and can certainly be loosened.
This should allow for more scalable usage of the QFR library when it is not used in conjunction with decision diagrams, e.g., for error correcting codes, compilation, stabilizer-based simulation of Clifford circuits.
At the moment, circuit 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).
Optimization passes should be applicable just like with non-parameterized circuits. This requires minor adjustments in the respective optimization passes. Some optimization passes (like single-qubit gate fusion) might even incorporate symbolic operations into the optimization.
I am not a fan of adding the generateDensityMatrix
flag to a ton of functions.
Unfortunately, C++ does not allow two functions that only differ in their return values. Otherwise it would be rather easy to get rid of this generateDensityMatrix
parameter, which is only relevant for density matrices.
One potential solution for this would be to refactor these methods in the following fashion:
template<class LeftOperandNode, class RightOperandNode, class ResultNode>
void multiply2(const const Edge<LeftOperandNode>& x, const Edge<RightOperandNode>& y, Edge<ResultNode>& result, Qubit var, Qubit start = 0) {
...
}
This would allow for specializations of Matrix * Matrix -> Matrix
and Matrix * Matrix -> Density Matrix
.
Originally posted by @burgholzer in cda-tum/dd_package#72 (comment)
How temporary is this deactivation? Something to be fixed before this PR is merged?
If not, then please create an issue for that. Otherwise I am fairly certain this will get lost.
Originally posted by @burgholzer in #210 (comment)
Currently, the ecc::Q18Surface error correction code only corrects bit flips.
The basic procedure for correcting phase errors is very similar to bit flip correction, however, only the initialization of ancilla qubits takes more effort and thus still has to be done.
Strategy: Starting with the 0-state, we perform a "measure-and-correct" step. To work correctly, this step has to detect and correct multiple errors at once. In the current implementation (already used for bit flip correction), multiple errors can be detected, but only single errors can be corrected.
The new OpenQASM standard introduces gate modifiers in order to more efficiently describe quantum circuits. Any modifier mod
can be applied to a gate g
via mod @ g
. For more details, see Section 4.2 in https://arxiv.org/pdf/2104.14722.pdf or the Live Specification.
The powering modifier pow(r)
can be used to represent the r-th power of any gate g
via pow(r) @ g
, where r
can either be an integer or a floating point number.
The case where r
is a floating point number needs to be handled by computing the principal logarithm of the unitary that represents g
. This might be tricky in general.
The case where r
is an integer can be naively solved by repeatedly applying g
. If r <0
, pow(r) @ g
can be translated to inv @ pow(-r) @ g
. Several simplification might be possible here, e.g., the powers of phase gates can easily be computed by appropriately scaling the respective parameter. In its simplest form pow(2) @ s = z
.
At the very least, support for the case where r
is an integer shall be provided which provides much greater flexibility for describing circuits in a standardized way.
In cda-tum/dd_package#144, a function was added to the DD package that allows the native construction of DDs for two-target gates. This is a significant improvement compared to the old-fashioned way of constructing these DDs from the DDs of a gate decomposition for the respective gate (e.g., a SWAP DD can be constructed from 2 CNOT DDs).
However, the implementation in cda-tum/dd_package#144 only works for non-controlled two-target gates. Thus, controlled versions of these gates still require the old-fashioned workaround. This can be seen in https://github.com/cda-tum/dd_package/blob/7c9644a2a65742bb580e3e5fefeac075c9534351/include/dd/Package.hpp#L629-L810
All of that code could be avoided by making the construction routine handle (arbitrary) controls as well. The single-target method already does this as shown in https://github.com/cda-tum/dd_package/blob/7c9644a2a65742bb580e3e5fefeac075c9534351/include/dd/Package.hpp#L480-L539
This requires some careful handling of all possible cases (control before first target, control between both targets, control after second target) and respective testing.
I believe this is a nice little project as it is very localized and would eliminate quite some code here and in our top-level modules.
A best case scenario would be to find a generic solution for multi-target gates, so that the makeGateDD
and makeTwoQubitGateDD
methods can be unified. Hypothetically that further abstraction shouldn't be too hard once one has figured out how to handle the "control between targets" case.
Currently, the DD package provides methods for directly constructing the decision diagrams for
|0>, |1>, |+>, |->, |L>, |R>
It would be convenient to have some further state preparations available that can be used, e.g., in DDSIM.
Examples:
It should be possible to directly construct the DDs for these states without starting from the all-zero state and applying some operations.
A suggestion would be to name these functions make<SOME-IDENTIFIER>State
.
In the interest of providing a little more structure in the DD package, these functions should probably be designed in a way that they can be put into a separate header file and, ideally, not rely on any template stuff, so that the implementation can be moved to a source file.
If this is accomplished, the existing functions for state preparation should be moved as well.
The new OpenQASM standard introduces many additional datatypes besides quantum and classical registers. This allows to describe classical components of quantum algorithms in a very intuitive way. As a first step, support for all kinds of new types should be added to the QFR library and, correspondingly, to the QuantumComputation class. For details on all the new typed provided by the standard, see https://qiskit.github.io/openqasm/language/classical.html.
The standard provides the means for converting between the available types. For details, see https://qiskit.github.io/openqasm/language/types.html#casting-specifics.
This depends on #30 for the availability of all the types.
The DD package as it is currently implemented relies on the memory addresses of nodes for hashing (pointer-based DD package). Modern compilers and operating systems implement Address Space Layout Randomization (ASLR) as a security measure to randomize the location of code.
This, however, leads to non-deterministic behavior of the decision diagram package because different invocations of the same computation are allocated in different memory regions and hence hash values might differ.
It would be convenient, especially for debugging, to have an option for disabling ASLR and, thus, enabling reproducible builds.
AFAIK, the way to achieve this is platform-specific, but there should be enough about this out there.
Ideally, it gets integrated into the cmake/
folder as an additional standalone script.
Note
This issue becomes somewhat obsolete if we ever decide to switch to an index-based DD package implementation.
This is a tracking issue for adding QIR support within the MQT.
The following contains a summary of some discussions that took place at QCE23.
One of the key take aways is that it is fine to keep our own circuit representation, i.e., we are not expected to base all of our developments on an LLVM Parse-Tree and throw away the QuantumComputation
class.
In principle, we need to be able to parse QIR into QuantumComputation
objects and create QIR from a QuantumComputation
description.
The ad-hoc, straight-forward solution would be to rely on the PyQIR Python library that facilitates easy parsing and writing of QIR.
As for translating our circuit structure to QIR, https://github.com/microsoft/qiskit-qir could serve as an inspiration.
Pros:
A potential alternative would be to use the qirlib Rust library that builds the foundation for PyQIR and interface with that.
Pros:
As a consequence of the above, it was actually recommended to directly build upon LLVM, which is natively written in C++.
Pros:
qirlib
and our in-house solutionAs for the last part, I was told that the part of LLVM that’s actually needed for QIR support should be rather small compared to the complete LLVM Suite.
From these discussion, I would rule out the second option from above as it sits straight in between the least effort/quickest solution and the most effort/long-term solution.
I would prefer the long-term solution to be implemented right away, although it might be a tough nut to crack.
A couple of references for starting out with this:
The DD package contains a “fine collection” of (non-const) global variables. This includes the terminal nodes for DDs as well as the real number constants for 0., 1., 1./sqrt(2) and the complex number constants zero and one. All of these are defined as (non-const) static variables with global scope. This has a number of drawbacks:
This list of undesirable behavior is arguably quite long. So what could be done about it?
Since we are heavily dealing with pointer types, there is one rather straight forward solution: replace these global (non-)constants with clever uses of nullptr.
For DD terminal nodes, that is fairly straightforward. Up until now, a terminal node was a pointer to a global static Node that had variable index -1. The successors of that node were never accessed and any check for a terminal either compared addresses with the pointer to the terminal node or checked the variable index. This kind of usage of the variable index necessitated a signed type for that variable and, hence, halved the maximal number of qubits that could be supported by the package from 255 to 128.
The notion of a terminal can simply be replaced by the nullptr without any apparent downsides.
For the real number constants, the story is not so easy. The main problem here is that there is not a single, but three different constants that are being used (0., 1., and 1/sqrt(2)). Thus, these constants can’t simply all be replaced by using nullptr. Furthermore, the value of these entries are actually used in computations. However, there is a potential solution for that:
We already make use of the LSB of pointers to numbers to indicate that they are negative. Why not turn that up a notch and make use of the available scratch space in the lower bits of the pointers? (Due to alignment reasons, the lower three bits of any valid pointer are zero, which leaves 3 bits for hiding some stuff in pointers). The following hinges on one assumption: nullptr lives at address 0x0. Instead of creating global (non-)constants, the nullptr and its least significant three bits can be used to encode these special values:
The good thing is: these two changes are independent from one another and can happen in parallel (or the complex number change might not happen at all).
Both, the DD package and the QFR, contain their own definition of a Control
. This is mainly due to historical reasons and the split between the two packages.
Now that both packages are one, these duplicate structures can be eliminated completely.
The QFR already provides the means to import a Qiskit QuantumCircuit
into our own C++ qc::QuantumComputation
(see https://github.com/iic-jku/qfr/blob/master/jkq/qfr/qiskit/QuantumCircuit.hpp).
In the future, it might be beneficial to provide support for translating a C++ qc::QuantumComputation
to a Qiskit QuantumCircuit
. At the moment, this is handled via translating to QASM and then loading the resulting string into Qiskit.
Possible applications could be the compilation of quantum circuits.
Important aspects to think about are how the initial_layout
and the output_permutation
are translated to Qiskit.
As of (I believe) qiskit-terra>=0.23
, circuits have a _layout
property that can store the initial_layout
as well as the final_layout
of the circuit.
In addition, the final layout should be indicated via measurements at the end of the circuits.
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.