Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ added inverse gate for iSWAP #460

Merged
merged 14 commits into from
Nov 18, 2023

Conversation

BertiFlorea
Copy link
Collaborator

@BertiFlorea BertiFlorea commented Oct 25, 2023

Description

The DD Package was missing the iSWAPDag (iSWAP dagger) gate. However, the DD Package already had in some place a ISWAPDG_MAT which implemented the desired gate in a matrix form.

Status

Currently, the iSWAPdagDumpIsValid test produces the following error:

gate type iswapdg could not be converted to OpenQASM

i:   0   1
1:iswdiswd
o:   0   1

~/mqt-core/test/unittests/test_io.cpp:389: Failure
Expected: qc->import(ss, qc::Format::OpenQASM); doesn't throw an exception.
  Actual: it throws std::runtime_error with description "[qasm parser] l:6 c:2 msg: Unknown gate q".


i:   0   1
o:   0   1

After reading through https://qiskit.org/documentation/apidoc/circuit_library.html, my assumption is that OpenQASM does not support at the moment the iSWAP dagger gate natively, so, therefore, it doesn't have implementation for it.

(I know that the previous assumption might not be the reason why, but I tried to point out a tiny direction why this might not work. Anyway, I will try to look further into this issue in the near future)

Fixes #423

Checklist:

  • The pull request only contains commits that are related to it.
  • I have added appropriate tests and documentation.
  • I have made sure that all CI jobs on GitHub pass.
  • The pull request introduces no new warnings and follows the project's style guidelines.

@burgholzer burgholzer added enhancement New feature or request Core Anything related to the Core library and IR c++ Anything related to C++ code labels Oct 25, 2023
@BertiFlorea
Copy link
Collaborator Author

BertiFlorea commented Oct 26, 2023

Important note: iSWAP is supported by OpenQASM, however, the inverse isn't, therefore I didn't know precisely how to proceed further. (Also, I wasn't sure what files I was/wasn't allowed to modify)

In other words: if the gate doesn't exist per se, technically I am not allowed to add it in the test file test/circuits/test.qasm and that way the code coverage will drop.

Copy link
Member

@burgholzer burgholzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here: many thanks for this addition! This is looking really good already and I believe you managed to cover almost all the places.
I left some comments inline.
Other than that, it's really only the C++ coverage check that is complaining. You can directly inspect the coverage report by clicking on the respective link in the comment posted by codecov. I believe there should be further existing tests that you can add this new gate to in order to cover the missing lines.

@codecov
Copy link

codecov bot commented Oct 29, 2023

Codecov Report

Merging #460 (857808d) into main (95541a6) will increase coverage by 0.0%.
The diff coverage is 95.0%.

Additional details and impacted files

Impacted file tree graph

@@          Coverage Diff          @@
##            main    #460   +/-   ##
=====================================
  Coverage   90.1%   90.1%           
=====================================
  Files        111     111           
  Lines      11845   11866   +21     
  Branches    2065    2066    +1     
=====================================
+ Hits       10677   10702   +25     
+ Misses      1168    1164    -4     
Flag Coverage Δ
cpp 89.9% <95.0%> (+<0.1%) ⬆️
python 100.0% <ø> (ø)
Files Coverage Δ
include/QuantumComputation.hpp 90.7% <100.0%> (+<0.1%) ⬆️
include/operations/OpType.hpp 87.4% <100.0%> (+0.4%) ⬆️
include/parsers/qasm_parser/Parser.hpp 100.0% <ø> (ø)
src/operations/StandardOperation.cpp 92.8% <100.0%> (+0.1%) ⬆️
include/dd/Operations.hpp 84.8% <85.7%> (+2.1%) ⬆️

Copy link
Member

@burgholzer burgholzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! many thanks!

Now that the new gate is in, a natural follow-up would be to consolidate the following methods:

  • void StandardOperation::invert() {
    switch (type) {
    // self-inverting gates
    case I:
    case X:
    case Y:
    case Z:
    case H:
    case SWAP:
    case ECR:
    case Barrier:
    break;
    // gates where we just update parameters
    case GPhase:
    case P:
    case RX:
    case RY:
    case RZ:
    case RXX:
    case RYY:
    case RZZ:
    case RZX:
    parameter[0] = -parameter[0];
    break;
    case U2:
    std::swap(parameter[0], parameter[1]);
    parameter[0] = -parameter[0] + PI;
    parameter[1] = -parameter[1] - PI;
    break;
    case U:
    parameter[0] = -parameter[0];
    parameter[1] = -parameter[1];
    parameter[2] = -parameter[2];
    std::swap(parameter[1], parameter[2]);
    break;
    case XXminusYY:
    case XXplusYY:
    parameter[0] = -parameter[0];
    break;
    case DCX:
    std::swap(targets[0], targets[1]);
    break;
    // gates where we have specialized inverted operation types
    case S:
    type = Sdg;
    break;
    case Sdg:
    type = S;
    break;
    case T:
    type = Tdg;
    break;
    case Tdg:
    type = T;
    break;
    case V:
    type = Vdg;
    break;
    case Vdg:
    type = V;
    break;
    case SX:
    type = SXdg;
    break;
    case SXdg:
    type = SX;
    break;
    case Peres:
    type = Peresdg;
    break;
    case Peresdg:
    type = Peres;
    break;
    // Tracking issue for iSwap: https://github.com/cda-tum/mqt-core/issues/423
    case iSWAP:
    case None:
    case Compound:
    case Measure:
    case Reset:
    case Teleportation:
    case ClassicControlled:
    case ATrue:
    case AFalse:
    case MultiATrue:
    case MultiAFalse:
    case OpCount:
    throw QFRException("Inverting gate" + toString(type) +
    " is not supported.");
    }
  • switch (type) {
    case qc::I:
    gm = I_MAT;
    break;
    case qc::H:
    gm = H_MAT;
    break;
    case qc::X:
    gm = X_MAT;
    break;
    case qc::Y:
    gm = Y_MAT;
    break;
    case qc::Z:
    gm = Z_MAT;
    break;
    case qc::S:
    gm = inverse ? SDG_MAT : S_MAT;
    break;
    case qc::Sdg:
    gm = inverse ? S_MAT : SDG_MAT;
    break;
    case qc::T:
    gm = inverse ? TDG_MAT : T_MAT;
    break;
    case qc::Tdg:
    gm = inverse ? T_MAT : TDG_MAT;
    break;
    case qc::V:
    gm = inverse ? VDG_MAT : V_MAT;
    break;
    case qc::Vdg:
    gm = inverse ? V_MAT : VDG_MAT;
    break;
    case qc::U:
    gm = inverse ? uMat(-parameter[1U], -parameter[2U], -parameter[0U])
    : uMat(parameter[2U], parameter[1U], parameter[0U]);
    break;
    case qc::U2:
    gm = inverse ? u2Mat(-parameter[0U] + PI, -parameter[1U] - PI)
    : u2Mat(parameter[1U], parameter[0U]);
    break;
    case qc::P:
    gm = inverse ? pMat(-parameter[0U]) : pMat(parameter[0U]);
    break;
    case qc::SX:
    gm = inverse ? SXDG_MAT : SX_MAT;
    break;
    case qc::SXdg:
    gm = inverse ? SX_MAT : SXDG_MAT;
    break;
    case qc::RX:
    gm = inverse ? rxMat(-parameter[0U]) : rxMat(parameter[0U]);
    break;
    case qc::RY:
    gm = inverse ? ryMat(-parameter[0U]) : ryMat(parameter[0U]);
    break;
    case qc::RZ:
    gm = inverse ? rzMat(-parameter[0U]) : rzMat(parameter[0U]);
    break;
    default:
    std::ostringstream oss{};
    oss << "DD for gate" << op->getName() << " not available!";
    throw qc::QFRException(oss.str());
    }
    return dd->makeGateDD(gm, nqubits, controls, target, startQubit);
    }
  • switch (type) {
    case qc::SWAP:
    gm = SWAP_MAT;
    break;
    case qc::iSWAP:
    gm = inverse ? ISWAPDG_MAT : ISWAP_MAT;
    break;
    case qc::DCX:
    gm = DCX_MAT;
    break;
    case qc::ECR:
    gm = ECR_MAT;
    break;
    case qc::RXX:
    gm = inverse ? rxxMat(-parameter[0U]) : rxxMat(parameter[0U]);
    break;
    case qc::RYY:
    gm = inverse ? ryyMat(-parameter[0U]) : ryyMat(parameter[0U]);
    break;
    case qc::RZZ:
    gm = inverse ? rzzMat(-parameter[0U]) : rzzMat(parameter[0U]);
    break;
    case qc::RZX:
    gm = inverse ? rzxMat(-parameter[0U]) : rzxMat(parameter[0U]);
    break;
    case qc::XXminusYY:
    gm = inverse ? xxMinusYYMat(-parameter[0U], parameter[1U])
    : xxMinusYYMat(parameter[0U], parameter[1U]);
    break;
    case qc::XXplusYY:
    gm = inverse ? xxPlusYYMat(-parameter[0U], parameter[1U])
    : xxPlusYYMat(parameter[0U], parameter[1U]);
    break;
    default:
    definitionFound = false;
    }
    if (definitionFound) {
    return dd->makeTwoQubitGateDD(gm, nqubits, target0, target1, startQubit);
    }
    }
    switch (type) {
    case qc::SWAP:
    // SWAP is self-inverse
    return dd->makeSWAPDD(nqubits, controls, target0, target1, startQubit);
    case qc::iSWAP:
    if (inverse) {
    return dd->makeiSWAPinvDD(nqubits, controls, target0, target1,
    startQubit);
    }
    return dd->makeiSWAPDD(nqubits, controls, target0, target1, startQubit);
    case qc::Peres:
    if (inverse) {
    return dd->makePeresdagDD(nqubits, controls, target0, target1,
    startQubit);
    }
    return dd->makePeresDD(nqubits, controls, target0, target1, startQubit);
    case qc::Peresdg:
    if (inverse) {
    return dd->makePeresDD(nqubits, controls, target0, target1, startQubit);
    }
    return dd->makePeresdagDD(nqubits, controls, target0, target1, startQubit);
    case qc::DCX:
    return dd->makeDCXDD(nqubits, controls, target0, target1, startQubit);
    case qc::ECR:
    // ECR is self-inverse
    return dd->makeECRDD(nqubits, controls, target0, target1, startQubit);
    case qc::RXX: {
    if (inverse) {
    return dd->makeRXXDD(nqubits, controls, target0, target1, -parameter[0U],
    startQubit);
    }
    return dd->makeRXXDD(nqubits, controls, target0, target1, parameter[0U],
    startQubit);
    }
    case qc::RYY: {
    if (inverse) {
    return dd->makeRYYDD(nqubits, controls, target0, target1, -parameter[0U],
    startQubit);
    }
    return dd->makeRYYDD(nqubits, controls, target0, target1, parameter[0U],
    startQubit);
    }
    case qc::RZZ: {
    if (inverse) {
    return dd->makeRZZDD(nqubits, controls, target0, target1, -parameter[0U],
    startQubit);
    }
    return dd->makeRZZDD(nqubits, controls, target0, target1, parameter[0U],
    startQubit);
    }
    case qc::RZX: {
    if (inverse) {
    return dd->makeRZXDD(nqubits, controls, target0, target1, -parameter[0U],
    startQubit);
    }
    return dd->makeRZXDD(nqubits, controls, target0, target1, parameter[0U],
    startQubit);
    }
    case qc::XXminusYY: {
    if (inverse) {
    return dd->makeXXMinusYYDD(nqubits, controls, target0, target1,
    -parameter[0U], parameter[1U], startQubit);
    }
    return dd->makeXXMinusYYDD(nqubits, controls, target0, target1,
    parameter[0U], parameter[1U], startQubit);
    }
    case qc::XXplusYY: {
    if (inverse) {
    return dd->makeXXPlusYYDD(nqubits, controls, target0, target1,
    -parameter[0U], parameter[1U], startQubit);
    }
    return dd->makeXXPlusYYDD(nqubits, controls, target0, target1,
    parameter[0U], parameter[1U], startQubit);
    }
    default:
    std::ostringstream oss{};
    oss << "DD for gate" << op->getName() << " not available!";
    throw qc::QFRException(oss.str());
    }
    }

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:

  • there would be an 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.
  • The challenge will be to get the interface right so that there is a standardized way to call the newly introduced methods. In that regard, further compile-time/constexpr convenience functions like isSingleTargetGate, isTwoTargetGate, and isThreeOrMoreTargetGate would probably make sense.
  • Naturally, this creates quite some new classes, but I believe it makes the library structure a little simpler. Needs a little experimenting on the refactor though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Anything related to C++ code Core Anything related to the Core library and IR enhancement New feature or request
Projects
Status: Done
Status: Done
Development

Successfully merging this pull request may close these issues.

✨ Inverse gate for iSwap
2 participants