From 1b989e25eb8e4eb79c4c30a2a379b868f0d820a1 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 12 Jun 2024 22:45:23 +0200 Subject: [PATCH 01/34] Added implementation of RealParser handling for .inputs, .outputs, .constants and .garbage entries in .real file --- src/parsers/RealParser.cpp | 421 ++++++++++++++++++++++++++++++++++--- 1 file changed, 394 insertions(+), 27 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 4bddfb1fb..ef19b636d 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -2,6 +2,111 @@ #include +std::optional getQubitForVariableIdentFromAnyLookup( + const std::string& variableIdent, const qc::QuantumRegisterMap& dataQubits, + const qc::QuantumRegisterMap& ancillaryQubits) { + if (const auto& matchingEntryInDataQubits = dataQubits.find(variableIdent); + matchingEntryInDataQubits != dataQubits.end()) + return matchingEntryInDataQubits->second.first; + + if (const auto& matchingEntryInAncillaryQubits = + ancillaryQubits.find(variableIdent); + matchingEntryInAncillaryQubits != ancillaryQubits.end()) + return matchingEntryInAncillaryQubits->second.first; + + return std::nullopt; +} + +/// Determine whether the given io name value that is not enclosed in quotes +/// consists of only letters, digits and underscore characters. +/// @param ioName The name to valid +/// @return Whether the given io name is valid +bool isValidIoName(const std::string_view& ioName) { + if (ioName.empty()) + return false; + + if (ioName.front() == '"') + return true; + + return std::all_of( + ioName.cbegin(), ioName.cend(), [](const char ioNameCharacter) { + return std::isalnum(ioNameCharacter) || ioNameCharacter == '_'; + }); +} + +std::unordered_map +parseIoNames(std::size_t lineInRealFileDefiningIoNames, + std::size_t expectedNumberOfIos, + const std::string& ioNameIdentsRawValues) { + std::unordered_map foundIoNames; + std::size_t ioNameStartIdx = 0; + std::size_t ioNameEndIdx; + std::size_t ioIdx = 0; + + while (ioNameStartIdx < ioNameIdentsRawValues.size() && + foundIoNames.size() <= expectedNumberOfIos) { + bool searchingForWhitespaceCharacter = + ioNameIdentsRawValues.at(ioNameStartIdx) != '"'; + if (searchingForWhitespaceCharacter) + ioNameEndIdx = ioNameIdentsRawValues.find_first_of(' ', ioNameStartIdx); + else + ioNameEndIdx = ioNameIdentsRawValues.find_first_of('"', ioNameStartIdx); + + if (ioNameEndIdx == std::string::npos) { + ioNameEndIdx = ioNameIdentsRawValues.size(); + if (!searchingForWhitespaceCharacter) { + throw qc::QFRException( + "[real parser] l: " + + std::to_string(lineInRealFileDefiningIoNames) + + " no matching closing quote found for name of io: " + + std::to_string(ioIdx)); + } + } else { + ioNameEndIdx += !searchingForWhitespaceCharacter; + } + + std::size_t ioNameLength = ioNameEndIdx - ioNameStartIdx; + // On windows the line ending could be the character sequence \r\n while on + // linux system it would only be \n + if (ioNameLength && + ioNameIdentsRawValues.at( + std::min(ioNameEndIdx, ioNameIdentsRawValues.size() - 1)) == '\r') + --ioNameLength; + + const std::string ioName = + ioNameIdentsRawValues.substr(ioNameStartIdx, ioNameLength); + + /*std::cout << "IDX: " << std::to_string(ioIdx) << " " << ioName << " start: + " << std::to_string(ioNameStartIdx) << " end: " + << std::to_string(ioNameEndIdx) << " length: " + + std::to_string(ioNameLength) + << "\n";*/ + + if (!isValidIoName(ioName)) { + throw qc::QFRException( + "[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + + " invalid io name: " + ioName); + } + + ioNameStartIdx = ioNameEndIdx + searchingForWhitespaceCharacter; + /* + * We offer the user the use of some special literals to denote either constant inputs + * or garbage outputs instead of finding unique names for said ios, otherwise check that the + * given io name is unique. + */ + if (!(ioName == "0" || ioName == "1" || ioName == "g")) { + if (const auto& ioNameInsertionIntoLookupResult = + foundIoNames.emplace(ioName, static_cast(ioIdx++)); + !ioNameInsertionIntoLookupResult.second) { + throw qc::QFRException("[real parser] l: " + + std::to_string(lineInRealFileDefiningIoNames) + + "duplicate io name: " + ioName); + } + } + } + return foundIoNames; +} + void qc::QuantumComputation::importReal(std::istream& is) { auto line = readRealHeader(is); readRealGateDescriptions(is, line); @@ -12,6 +117,13 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { std::string variable; int line = 0; + /* + * We could reuse the QuantumRegisterMap type defined in the qc namespace but + * to avoid potential errors due to any future refactoring of said type, we + * use an std::unordered_map instead + */ + std::unordered_map userDefinedInputIdents; + while (true) { if (!static_cast(is >> cmd)) { throw QFRException("[real parser] l:" + std::to_string(line) + @@ -39,6 +151,9 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { return line; } + // TODO: Iterating over nqubits variable might not work when qubits between + // nqregs and ancillary lookup are transferred and nqubits is decremented + if (cmd == ".NUMVARS") { std::size_t nq{}; if (!static_cast(is >> nq)) { @@ -48,7 +163,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } nclassics = nqubits; } else if (cmd == ".VARIABLES") { - for (std::size_t i = 0; i < nqubits; ++i) { + for (std::size_t i = 0; i < nclassics; ++i) { if (!static_cast(is >> variable) || variable.at(0) == '.') { throw QFRException( "[real parser] l:" + std::to_string(line) + @@ -62,28 +177,112 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { ancillary.resize(nqubits); garbage.resize(nqubits); } + + // TODO: Check whether more than the declared number of variables was defined } else if (cmd == ".CONSTANTS") { is >> std::ws; - for (std::size_t i = 0; i < nqubits; ++i) { - const auto value = is.get(); - if (!is.good()) { + for (std::size_t i = 0; i < nclassics; ++i) { + char readConstantFlagValue; + if (!is.get(readConstantFlagValue)) { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Failed read in '.constants' line"); } - if (value == '1') { - x(static_cast(i)); - } else if (value != '-' && value != '0') { + + if (const bool isCurrentQubitMarkedAsAncillary = + readConstantFlagValue == '0' || readConstantFlagValue == '1'; + isCurrentQubitMarkedAsAncillary) { + const Qubit ancillaryQubit = static_cast(i); + setLogicalQubitAncillary(ancillaryQubit); + + // TODO: Is this step really necessary? + /* + * Since the call to setLogicalQubitAncillary does not actually + * transfer the qubit between the two register maps, said step must be + * performed manually + */ + const std::string& associatedVariableNameForQubitRegister = + getQubitRegister(ancillaryQubit); + qregs.erase(associatedVariableNameForQubitRegister); + ancregs.insert_or_assign( + associatedVariableNameForQubitRegister, + qc::QuantumRegister(std::make_pair(ancillaryQubit, 1U))); + } else if (readConstantFlagValue != '-') { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Invalid value in '.constants' header: '" + - std::to_string(value) + "'"); + std::to_string(readConstantFlagValue) + "'"); } } is.ignore(std::numeric_limits::max(), '\n'); - } else if (cmd == ".INPUTS" || cmd == ".OUTPUTS" || cmd == ".GARBAGE" || - cmd == ".VERSION" || cmd == ".INPUTBUS" || cmd == ".OUTPUTBUS") { - // TODO .inputs: specifies initial layout (and ancillaries) - // TODO .outputs: specifies output permutation - // TODO .garbage: specifies garbage outputs + } else if (cmd == ".GARBAGE") { + is >> std::ws; + for (std::size_t i = 0; i < nclassics; ++i) { + char readGarbageStatusFlagValue; + if (!is.get(readGarbageStatusFlagValue)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in '.garbage' line"); + } + + if (const bool isCurrentQubitMarkedAsGarbage = + readGarbageStatusFlagValue == '1'; + isCurrentQubitMarkedAsGarbage) { + setLogicalQubitGarbage(static_cast(i)); + } else if (readGarbageStatusFlagValue != '-') { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Invalid value in '.garbage' header: '" + + std::to_string(readGarbageStatusFlagValue) + "'"); + } + } + is.ignore(std::numeric_limits::max(), '\n'); + } else if (cmd == ".INPUTS") { + // .INPUT: specifies initial layout + is >> std::ws; + const std::size_t expectedNumInputIos = nclassics; + std::string ioNameIdentsLine; + std::getline(is, ioNameIdentsLine); + + userDefinedInputIdents = + parseIoNames(line, expectedNumInputIos, ioNameIdentsLine); + + if (userDefinedInputIdents.size() != expectedNumInputIos) { + std::cout << userDefinedInputIdents.size() << "\n"; + throw QFRException( + "[real parser} l: " + std::to_string(line) + "msg: Expected " + + std::to_string(expectedNumInputIos) + " inputs to be declared!"); + } + } else if (cmd == ".OUTPUTS") { + // .OUTPUTS: specifies output permutation + is >> std::ws; + const std::size_t expectedNumOutputIos = nclassics; + std::string ioNameIdentsLine; + std::getline(is, ioNameIdentsLine); + + const std::unordered_map userDefinedOutputIdents = + parseIoNames(line, expectedNumOutputIos, ioNameIdentsLine); + + if (userDefinedOutputIdents.size() != expectedNumOutputIos) { + std::cout << userDefinedOutputIdents.size() << "\n"; + throw QFRException( + "[real parser} l: " + std::to_string(line) + "msg: Expected " + + std::to_string(expectedNumOutputIos) + " outputs to be declared!"); + } + + for (const auto& [inputIoIdent, inputIoQubit] : userDefinedInputIdents) { + /* If we do not find a matching output label for the given input, we assume that the output io + * at the position of the current io is a garbage one and thus remove it from the output permutation. + * + * Since outputs marked as garbage are already removed from the outputPermutation lookup, + * could we require that every remaining input has a matching output defined? + */ + if (!userDefinedOutputIdents.count(inputIoIdent)) { + outputPermutation.erase(inputIoQubit); + } else { + const qc::Qubit outputIoQubit = userDefinedOutputIdents.at(inputIoIdent); + if (inputIoQubit != outputIoQubit) + outputPermutation.insert_or_assign(inputIoQubit, outputIoQubit); + } + + } + } else if (cmd == ".VERSION" || cmd == ".INPUTBUS" || cmd == ".OUTPUTBUS") { is.ignore(std::numeric_limits::max(), '\n'); continue; } else if (cmd == ".DEFINE") { @@ -105,6 +304,8 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } } +// TODO: If no register transfer between lookups happen, update function for +// lookup of qubit accordingly void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, int line) { const std::regex gateRegex = @@ -168,10 +369,10 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, ncontrols = 2; } - if (ncontrols >= nqubits) { + if (ncontrols >= getNqubits()) { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Gate acts on " + std::to_string(ncontrols + 1) + - " qubits, but only " + std::to_string(nqubits) + + " qubits, but only " + std::to_string(getNqubits()) + " qubits are available."); } @@ -194,14 +395,17 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, label.erase(label.begin()); } - auto iter = qregs.find(label); - if (iter == qregs.end()) { + // Since variable qubits can either be data or ancillary qubits our search + // will have to be conducted in both lookups + const std::optional controlLineQubit = + getQubitForVariableIdentFromAnyLookup(label, qregs, ancregs); + if (!controlLineQubit) { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Label " + label + " not found!"); } - controls.emplace_back(iter->second.first, negativeControl - ? Control::Type::Neg - : Control::Type::Pos); + controls.emplace_back(*controlLineQubit, negativeControl + ? Control::Type::Neg + : Control::Type::Pos); } if (!(iss >> label)) { @@ -209,13 +413,16 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, " msg: Too few variables (no target) for gate " + m.str(1)); } - auto iter = qregs.find(label); - if (iter == qregs.end()) { + + // Since variable qubits can either be data or ancillary qubits our search + // will have to be conducted in both lookups + const std::optional targetLineQubit = + getQubitForVariableIdentFromAnyLookup(label, qregs, ancregs); + if (!targetLineQubit) { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Label " + label + " not found!"); } - const Qubit target = iter->second.first; switch (gate) { case I: case H: @@ -228,17 +435,17 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, case V: case Vdg: emplace_back( - Controls{controls.cbegin(), controls.cend()}, target, gate); + Controls{controls.cbegin(), controls.cend()}, *targetLineQubit, gate); break; case X: - mcx(Controls{controls.cbegin(), controls.cend()}, target); + mcx(Controls{controls.cbegin(), controls.cend()}, *targetLineQubit); break; case RX: case RY: case RZ: case P: emplace_back( - Controls{controls.cbegin(), controls.cend()}, target, gate, + Controls{controls.cbegin(), controls.cend()}, *targetLineQubit, gate, std::vector{PI / (lambda)}); break; case SWAP: @@ -248,7 +455,8 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, const auto target1 = controls.back().qubit; controls.pop_back(); emplace_back( - Controls{controls.cbegin(), controls.cend()}, target1, target, gate); + Controls{controls.cbegin(), controls.cend()}, target1, + *targetLineQubit, gate); break; } default: @@ -257,3 +465,162 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, } } } + +// BACKUP OF ORIGINAL +// void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, +// int line) { +// const std::regex gateRegex = +// std::regex("(r[xyz]|i[df]|q|[0a-z](?:[+ip])?)(\\d+)?(?::([-+]?[0-9]+[.]?[" +// "0-9]*(?:[eE][-+]?[0-9]+)?))?"); +// std::smatch m; +// std::string cmd; +// +// static const std::map IDENTIFIER_MAP{ +// {"0", I}, {"id", I}, {"h", H}, {"n", X}, {"c", X}, +// {"x", X}, {"y", Y}, {"z", Z}, {"s", S}, {"si", +// Sdg}, +// {"sp", Sdg}, {"s+", Sdg}, {"v", V}, {"vi", Vdg}, {"vp", +// Vdg}, +// {"v+", Vdg}, {"rx", RX}, {"ry", RY}, {"rz", RZ}, {"f", +// SWAP}, +// {"if", SWAP}, {"p", Peres}, {"pi", Peresdg}, {"p+", Peresdg}, {"q", P}}; +// +// while (!is.eof()) { +// if (!static_cast(is >> cmd)) { +// throw QFRException("[real parser] l:" + std::to_string(line) + +// " msg: Failed to read command"); +// } +// std::transform( +// cmd.begin(), cmd.end(), cmd.begin(), +// [](const unsigned char c) { return static_cast(tolower(c)); }); +// ++line; +// +// if (cmd.front() == '#') { +// is.ignore(std::numeric_limits::max(), '\n'); +// continue; +// } +// +// if (cmd == ".end") { +// break; +// } +// +// // match gate declaration +// if (!std::regex_match(cmd, m, gateRegex)) { +// throw QFRException("[real parser] l:" + std::to_string(line) + +// " msg: Unsupported gate detected: " + cmd); +// } +// +// // extract gate information (identifier, #controls, divisor) +// OpType gate{}; +// if (m.str(1) == "t") { // special treatment of t(offoli) for real format +// gate = X; +// } else { +// auto it = IDENTIFIER_MAP.find(m.str(1)); +// if (it == IDENTIFIER_MAP.end()) { +// throw QFRException("[real parser] l:" + std::to_string(line) + +// " msg: Unknown gate identifier: " + m.str(1)); +// } +// gate = (*it).second; +// } +// auto ncontrols = +// m.str(2).empty() ? 0 : std::stoul(m.str(2), nullptr, 0) - 1; +// const fp lambda = m.str(3).empty() ? static_cast(0L) +// : +// static_cast(std::stold(m.str(3))); +// +// if (gate == V || gate == Vdg || m.str(1) == "c" || gate == SWAP) { +// ncontrols = 1; +// } else if (gate == Peres || gate == Peresdg) { +// ncontrols = 2; +// } +// +// if (ncontrols >= nqubits) { +// throw QFRException("[real parser] l:" + std::to_string(line) + +// " msg: Gate acts on " + std::to_string(ncontrols + 1) +// + " qubits, but only " + std::to_string(nqubits) + " +// qubits are available."); +// } +// +// std::string qubits; +// std::string label; +// getline(is, qubits); +// +// std::vector controls{}; +// std::istringstream iss(qubits); +// +// // get controls and target +// for (std::size_t i = 0; i < ncontrols; ++i) { +// if (!(iss >> label)) { +// throw QFRException("[real parser] l:" + std::to_string(line) + +// " msg: Too few variables for gate " + m.str(1)); +// } +// +// const bool negativeControl = (label.at(0) == '-'); +// if (negativeControl) { +// label.erase(label.begin()); +// } +// +// auto iter = qregs.find(label); +// if (iter == qregs.end()) { +// throw QFRException("[real parser] l:" + std::to_string(line) + +// " msg: Label " + label + " not found!"); +// } +// controls.emplace_back(iter->second.first, negativeControl +// ? Control::Type::Neg +// : Control::Type::Pos); +// } +// +// if (!(iss >> label)) { +// throw QFRException("[real parser] l:" + std::to_string(line) + +// " msg: Too few variables (no target) for gate " + +// m.str(1)); +// } +// auto iter = qregs.find(label); +// if (iter == qregs.end()) { +// throw QFRException("[real parser] l:" + std::to_string(line) + +// " msg: Label " + label + " not found!"); +// } +// +// const Qubit target = iter->second.first; +// switch (gate) { +// case I: +// case H: +// case Y: +// case Z: +// case S: +// case Sdg: +// case T: +// case Tdg: +// case V: +// case Vdg: +// emplace_back( +// Controls{controls.cbegin(), controls.cend()}, target, gate); +// break; +// case X: +// mcx(Controls{controls.cbegin(), controls.cend()}, target); +// break; +// case RX: +// case RY: +// case RZ: +// case P: +// emplace_back( +// Controls{controls.cbegin(), controls.cend()}, target, gate, +// std::vector{PI / (lambda)}); +// break; +// case SWAP: +// case Peres: +// case Peresdg: +// case iSWAP: { +// const auto target1 = controls.back().qubit; +// controls.pop_back(); +// emplace_back( +// Controls{controls.cbegin(), controls.cend()}, target1, target, +// gate); +// break; +// } +// default: +// std::cerr << "Unsupported operation encountered: " << gate << "!\n"; +// break; +// } +// } +//} From 00882f8a50ab6d00410973f8aca27a68e9b498e5 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Tue, 25 Jun 2024 21:07:02 +0200 Subject: [PATCH 02/34] Removed some dead code used for debugging and added mapping from outputs to inputs via output_permutation --- src/parsers/RealParser.cpp | 242 +++++++------------------------------ 1 file changed, 42 insertions(+), 200 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index ef19b636d..a64557da7 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -22,16 +22,11 @@ std::optional getQubitForVariableIdentFromAnyLookup( /// @param ioName The name to valid /// @return Whether the given io name is valid bool isValidIoName(const std::string_view& ioName) { - if (ioName.empty()) - return false; - - if (ioName.front() == '"') - return true; - - return std::all_of( - ioName.cbegin(), ioName.cend(), [](const char ioNameCharacter) { - return std::isalnum(ioNameCharacter) || ioNameCharacter == '_'; - }); + return !ioName.empty() && + std::all_of( + ioName.cbegin(), ioName.cend(), [](const char ioNameCharacter) { + return std::isalnum(ioNameCharacter) || ioNameCharacter == '_'; + }); } std::unordered_map @@ -76,12 +71,6 @@ parseIoNames(std::size_t lineInRealFileDefiningIoNames, const std::string ioName = ioNameIdentsRawValues.substr(ioNameStartIdx, ioNameLength); - /*std::cout << "IDX: " << std::to_string(ioIdx) << " " << ioName << " start: - " << std::to_string(ioNameStartIdx) << " end: " - << std::to_string(ioNameEndIdx) << " length: " + - std::to_string(ioNameLength) - << "\n";*/ - if (!isValidIoName(ioName)) { throw qc::QFRException( "[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + @@ -146,14 +135,16 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { " msg: Invalid file header"); } + /* + * TODO: Parsing currently assumes that expected entries are defined only once and in the expected + * definition order as defined in the .real header specification. + * (https://www.revlib.org/doc/docu/revlib_2_0_1.pdf Chapter 3.2) + */ if (cmd == ".BEGIN") { // header read complete return line; } - // TODO: Iterating over nqubits variable might not work when qubits between - // nqregs and ancillary lookup are transferred and nqubits is decremented - if (cmd == ".NUMVARS") { std::size_t nq{}; if (!static_cast(is >> nq)) { @@ -194,11 +185,10 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { const Qubit ancillaryQubit = static_cast(i); setLogicalQubitAncillary(ancillaryQubit); - // TODO: Is this step really necessary? /* * Since the call to setLogicalQubitAncillary does not actually - * transfer the qubit between the two register maps, said step must be - * performed manually + * transfer the qubit from the data qubit lookup into the ancillary lookup + * we will 'manually' perform this transfer. */ const std::string& associatedVariableNameForQubitRegister = getQubitRegister(ancillaryQubit); @@ -266,22 +256,35 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { std::to_string(expectedNumOutputIos) + " outputs to be declared!"); } - for (const auto& [inputIoIdent, inputIoQubit] : userDefinedInputIdents) { - /* If we do not find a matching output label for the given input, we assume that the output io - * at the position of the current io is a garbage one and thus remove it from the output permutation. - * - * Since outputs marked as garbage are already removed from the outputPermutation lookup, - * could we require that every remaining input has a matching output defined? - */ - if (!userDefinedOutputIdents.count(inputIoIdent)) { - outputPermutation.erase(inputIoQubit); - } else { - const qc::Qubit outputIoQubit = userDefinedOutputIdents.at(inputIoIdent); - if (inputIoQubit != outputIoQubit) - outputPermutation.insert_or_assign(inputIoQubit, outputIoQubit); - } - - } + for (const auto& [outputIoIdent, outputIoQubit] : + userDefinedOutputIdents) { + /* + * We assume that a permutation of a given input qubit Q at index i + * is performed in the circuit if an entry in both in the .output + * as well as the .input definition using the same literal is found + * with the input literal being defined at position i in the .input definition. + * If no such matching is found, we assume that the output is marked as garbage + * and thus remove the entry from the output permutation. + * + * The outputPermutation map will use be structured as shown in the documentation + * (https://mqt.readthedocs.io/projects/core/en/latest/quickstart.html#layout-information) + * with the output qubit being used as the key while the input qubit + * serves as the map entries value. + */ + if (!userDefinedInputIdents.count(outputIoIdent)) { + outputPermutation.erase(outputIoQubit); + } else if (const qc::Qubit matchingInputQubitForOutputLiteral = + userDefinedInputIdents.at(outputIoIdent); + matchingInputQubitForOutputLiteral != outputIoQubit) { + /* + * Only if the matching entries where defined at different indices + in their respective IO declaration + * do we update the existing 1-1 mapping for the given output qubit + */ + outputPermutation.insert_or_assign( + outputIoQubit, matchingInputQubitForOutputLiteral); + } + } } else if (cmd == ".VERSION" || cmd == ".INPUTBUS" || cmd == ".OUTPUTBUS") { is.ignore(std::numeric_limits::max(), '\n'); continue; @@ -304,8 +307,6 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } } -// TODO: If no register transfer between lookups happen, update function for -// lookup of qubit accordingly void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, int line) { const std::regex gateRegex = @@ -464,163 +465,4 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, break; } } -} - -// BACKUP OF ORIGINAL -// void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, -// int line) { -// const std::regex gateRegex = -// std::regex("(r[xyz]|i[df]|q|[0a-z](?:[+ip])?)(\\d+)?(?::([-+]?[0-9]+[.]?[" -// "0-9]*(?:[eE][-+]?[0-9]+)?))?"); -// std::smatch m; -// std::string cmd; -// -// static const std::map IDENTIFIER_MAP{ -// {"0", I}, {"id", I}, {"h", H}, {"n", X}, {"c", X}, -// {"x", X}, {"y", Y}, {"z", Z}, {"s", S}, {"si", -// Sdg}, -// {"sp", Sdg}, {"s+", Sdg}, {"v", V}, {"vi", Vdg}, {"vp", -// Vdg}, -// {"v+", Vdg}, {"rx", RX}, {"ry", RY}, {"rz", RZ}, {"f", -// SWAP}, -// {"if", SWAP}, {"p", Peres}, {"pi", Peresdg}, {"p+", Peresdg}, {"q", P}}; -// -// while (!is.eof()) { -// if (!static_cast(is >> cmd)) { -// throw QFRException("[real parser] l:" + std::to_string(line) + -// " msg: Failed to read command"); -// } -// std::transform( -// cmd.begin(), cmd.end(), cmd.begin(), -// [](const unsigned char c) { return static_cast(tolower(c)); }); -// ++line; -// -// if (cmd.front() == '#') { -// is.ignore(std::numeric_limits::max(), '\n'); -// continue; -// } -// -// if (cmd == ".end") { -// break; -// } -// -// // match gate declaration -// if (!std::regex_match(cmd, m, gateRegex)) { -// throw QFRException("[real parser] l:" + std::to_string(line) + -// " msg: Unsupported gate detected: " + cmd); -// } -// -// // extract gate information (identifier, #controls, divisor) -// OpType gate{}; -// if (m.str(1) == "t") { // special treatment of t(offoli) for real format -// gate = X; -// } else { -// auto it = IDENTIFIER_MAP.find(m.str(1)); -// if (it == IDENTIFIER_MAP.end()) { -// throw QFRException("[real parser] l:" + std::to_string(line) + -// " msg: Unknown gate identifier: " + m.str(1)); -// } -// gate = (*it).second; -// } -// auto ncontrols = -// m.str(2).empty() ? 0 : std::stoul(m.str(2), nullptr, 0) - 1; -// const fp lambda = m.str(3).empty() ? static_cast(0L) -// : -// static_cast(std::stold(m.str(3))); -// -// if (gate == V || gate == Vdg || m.str(1) == "c" || gate == SWAP) { -// ncontrols = 1; -// } else if (gate == Peres || gate == Peresdg) { -// ncontrols = 2; -// } -// -// if (ncontrols >= nqubits) { -// throw QFRException("[real parser] l:" + std::to_string(line) + -// " msg: Gate acts on " + std::to_string(ncontrols + 1) -// + " qubits, but only " + std::to_string(nqubits) + " -// qubits are available."); -// } -// -// std::string qubits; -// std::string label; -// getline(is, qubits); -// -// std::vector controls{}; -// std::istringstream iss(qubits); -// -// // get controls and target -// for (std::size_t i = 0; i < ncontrols; ++i) { -// if (!(iss >> label)) { -// throw QFRException("[real parser] l:" + std::to_string(line) + -// " msg: Too few variables for gate " + m.str(1)); -// } -// -// const bool negativeControl = (label.at(0) == '-'); -// if (negativeControl) { -// label.erase(label.begin()); -// } -// -// auto iter = qregs.find(label); -// if (iter == qregs.end()) { -// throw QFRException("[real parser] l:" + std::to_string(line) + -// " msg: Label " + label + " not found!"); -// } -// controls.emplace_back(iter->second.first, negativeControl -// ? Control::Type::Neg -// : Control::Type::Pos); -// } -// -// if (!(iss >> label)) { -// throw QFRException("[real parser] l:" + std::to_string(line) + -// " msg: Too few variables (no target) for gate " + -// m.str(1)); -// } -// auto iter = qregs.find(label); -// if (iter == qregs.end()) { -// throw QFRException("[real parser] l:" + std::to_string(line) + -// " msg: Label " + label + " not found!"); -// } -// -// const Qubit target = iter->second.first; -// switch (gate) { -// case I: -// case H: -// case Y: -// case Z: -// case S: -// case Sdg: -// case T: -// case Tdg: -// case V: -// case Vdg: -// emplace_back( -// Controls{controls.cbegin(), controls.cend()}, target, gate); -// break; -// case X: -// mcx(Controls{controls.cbegin(), controls.cend()}, target); -// break; -// case RX: -// case RY: -// case RZ: -// case P: -// emplace_back( -// Controls{controls.cbegin(), controls.cend()}, target, gate, -// std::vector{PI / (lambda)}); -// break; -// case SWAP: -// case Peres: -// case Peresdg: -// case iSWAP: { -// const auto target1 = controls.back().qubit; -// controls.pop_back(); -// emplace_back( -// Controls{controls.cbegin(), controls.cend()}, target1, target, -// gate); -// break; -// } -// default: -// std::cerr << "Unsupported operation encountered: " << gate << "!\n"; -// break; -// } -// } -//} +} \ No newline at end of file From b2241903c8a643b5f994d8c706f294d72a6ba9d9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 19:44:03 +0000 Subject: [PATCH 03/34] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parsers/RealParser.cpp | 84 ++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index cfab17e00..d87f3371b 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -94,9 +94,9 @@ parseIoNames(std::size_t lineInRealFileDefiningIoNames, ioNameStartIdx = ioNameEndIdx + searchingForWhitespaceCharacter; /* - * We offer the user the use of some special literals to denote either constant inputs - * or garbage outputs instead of finding unique names for said ios, otherwise check that the - * given io name is unique. + * We offer the user the use of some special literals to denote either + * constant inputs or garbage outputs instead of finding unique names for + * said ios, otherwise check that the given io name is unique. */ if (!(ioName == "0" || ioName == "1" || ioName == "g")) { if (const auto& ioNameInsertionIntoLookupResult = @@ -105,7 +105,7 @@ parseIoNames(std::size_t lineInRealFileDefiningIoNames, throw qc::QFRException("[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + "duplicate io name: " + ioName); - } + } } } return foundIoNames; @@ -151,9 +151,10 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } /* - * TODO: Parsing currently assumes that expected entries are defined only once and in the expected - * definition order as defined in the .real header specification. - * (https://www.revlib.org/doc/docu/revlib_2_0_1.pdf Chapter 3.2) + * TODO: Parsing currently assumes that expected entries are defined only + * once and in the expected definition order as defined in the .real header + * specification. (https://www.revlib.org/doc/docu/revlib_2_0_1.pdf + * Chapter 3.2) */ if (cmd == ".BEGIN") { // header read complete @@ -184,7 +185,8 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { garbage.resize(nqubits); } - // TODO: Check whether more than the declared number of variables was defined + // TODO: Check whether more than the declared number of variables was + // defined } else if (cmd == ".CONSTANTS") { is >> std::ws; for (std::size_t i = 0; i < nclassics; ++i) { @@ -202,8 +204,8 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { /* * Since the call to setLogicalQubitAncillary does not actually - * transfer the qubit from the data qubit lookup into the ancillary lookup - * we will 'manually' perform this transfer. + * transfer the qubit from the data qubit lookup into the ancillary + * lookup we will 'manually' perform this transfer. */ const std::string& associatedVariableNameForQubitRegister = getQubitRegister(ancillaryQubit); @@ -271,35 +273,37 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { std::to_string(expectedNumOutputIos) + " outputs to be declared!"); } - for (const auto& [outputIoIdent, outputIoQubit] : - userDefinedOutputIdents) { - /* - * We assume that a permutation of a given input qubit Q at index i - * is performed in the circuit if an entry in both in the .output - * as well as the .input definition using the same literal is found - * with the input literal being defined at position i in the .input definition. - * If no such matching is found, we assume that the output is marked as garbage - * and thus remove the entry from the output permutation. - * - * The outputPermutation map will use be structured as shown in the documentation - * (https://mqt.readthedocs.io/projects/core/en/latest/quickstart.html#layout-information) - * with the output qubit being used as the key while the input qubit - * serves as the map entries value. - */ - if (!userDefinedInputIdents.count(outputIoIdent)) { - outputPermutation.erase(outputIoQubit); - } else if (const qc::Qubit matchingInputQubitForOutputLiteral = - userDefinedInputIdents.at(outputIoIdent); - matchingInputQubitForOutputLiteral != outputIoQubit) { - /* - * Only if the matching entries where defined at different indices - in their respective IO declaration - * do we update the existing 1-1 mapping for the given output qubit - */ - outputPermutation.insert_or_assign( - outputIoQubit, matchingInputQubitForOutputLiteral); - } - } + for (const auto& [outputIoIdent, outputIoQubit] : + userDefinedOutputIdents) { + /* + * We assume that a permutation of a given input qubit Q at index i + * is performed in the circuit if an entry in both in the .output + * as well as the .input definition using the same literal is found + * with the input literal being defined at position i in the .input + * definition. If no such matching is found, we assume that the output + * is marked as garbage and thus remove the entry from the output + * permutation. + * + * The outputPermutation map will use be structured as shown in the + * documentation + * (https://mqt.readthedocs.io/projects/core/en/latest/quickstart.html#layout-information) + * with the output qubit being used as the key while the input qubit + * serves as the map entries value. + */ + if (!userDefinedInputIdents.count(outputIoIdent)) { + outputPermutation.erase(outputIoQubit); + } else if (const qc::Qubit matchingInputQubitForOutputLiteral = + userDefinedInputIdents.at(outputIoIdent); + matchingInputQubitForOutputLiteral != outputIoQubit) { + /* + * Only if the matching entries where defined at different indices + in their respective IO declaration + * do we update the existing 1-1 mapping for the given output qubit + */ + outputPermutation.insert_or_assign( + outputIoQubit, matchingInputQubitForOutputLiteral); + } + } } else if (cmd == ".VERSION" || cmd == ".INPUTBUS" || cmd == ".OUTPUTBUS") { is.ignore(std::numeric_limits::max(), '\n'); continue; @@ -480,4 +484,4 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, break; } } -} \ No newline at end of file +} From cbf2f2c9f966b5c308777fb19d390a2beaf77140 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 26 Jun 2024 22:38:02 +0200 Subject: [PATCH 04/34] Output qubits without matching input based on variable ident are only removed from output permutation if output is marked as garbage --- src/parsers/RealParser.cpp | 103 ++++++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 41 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index cfab17e00..61008542e 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -94,9 +94,9 @@ parseIoNames(std::size_t lineInRealFileDefiningIoNames, ioNameStartIdx = ioNameEndIdx + searchingForWhitespaceCharacter; /* - * We offer the user the use of some special literals to denote either constant inputs - * or garbage outputs instead of finding unique names for said ios, otherwise check that the - * given io name is unique. + * We offer the user the use of some special literals to denote either + * constant inputs or garbage outputs instead of finding unique names for + * said ios, otherwise check that the given io name is unique. */ if (!(ioName == "0" || ioName == "1" || ioName == "g")) { if (const auto& ioNameInsertionIntoLookupResult = @@ -105,7 +105,7 @@ parseIoNames(std::size_t lineInRealFileDefiningIoNames, throw qc::QFRException("[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + "duplicate io name: " + ioName); - } + } } } return foundIoNames; @@ -151,9 +151,10 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } /* - * TODO: Parsing currently assumes that expected entries are defined only once and in the expected - * definition order as defined in the .real header specification. - * (https://www.revlib.org/doc/docu/revlib_2_0_1.pdf Chapter 3.2) + * TODO: Parsing currently assumes that expected entries are defined only + * once and in the expected definition order as defined in the .real header + * specification. (https://www.revlib.org/doc/docu/revlib_2_0_1.pdf + * Chapter 3.2) */ if (cmd == ".BEGIN") { // header read complete @@ -183,8 +184,16 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { ancillary.resize(nqubits); garbage.resize(nqubits); } - - // TODO: Check whether more than the declared number of variables was defined + + /* + * TODO: Check whether more than the declared number of variables was + * defined + * + * TODO: Ancillary qubits are expected to be initialized with the value + * '0' but .real file .constants definition allows the two values '0' and + * '1' - do we have to manually insert appropriate gates to set the + * value qubits marked as constants to '1' ? + */ } else if (cmd == ".CONSTANTS") { is >> std::ws; for (std::size_t i = 0; i < nclassics; ++i) { @@ -202,8 +211,8 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { /* * Since the call to setLogicalQubitAncillary does not actually - * transfer the qubit from the data qubit lookup into the ancillary lookup - * we will 'manually' perform this transfer. + * transfer the qubit from the data qubit lookup into the ancillary + * lookup we will 'manually' perform this transfer. */ const std::string& associatedVariableNameForQubitRegister = getQubitRegister(ancillaryQubit); @@ -271,35 +280,47 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { std::to_string(expectedNumOutputIos) + " outputs to be declared!"); } - for (const auto& [outputIoIdent, outputIoQubit] : - userDefinedOutputIdents) { - /* - * We assume that a permutation of a given input qubit Q at index i - * is performed in the circuit if an entry in both in the .output - * as well as the .input definition using the same literal is found - * with the input literal being defined at position i in the .input definition. - * If no such matching is found, we assume that the output is marked as garbage - * and thus remove the entry from the output permutation. - * - * The outputPermutation map will use be structured as shown in the documentation - * (https://mqt.readthedocs.io/projects/core/en/latest/quickstart.html#layout-information) - * with the output qubit being used as the key while the input qubit - * serves as the map entries value. - */ - if (!userDefinedInputIdents.count(outputIoIdent)) { - outputPermutation.erase(outputIoQubit); - } else if (const qc::Qubit matchingInputQubitForOutputLiteral = - userDefinedInputIdents.at(outputIoIdent); - matchingInputQubitForOutputLiteral != outputIoQubit) { - /* - * Only if the matching entries where defined at different indices - in their respective IO declaration - * do we update the existing 1-1 mapping for the given output qubit - */ - outputPermutation.insert_or_assign( - outputIoQubit, matchingInputQubitForOutputLiteral); - } - } + for (const auto& [outputIoIdent, outputIoQubit] : + userDefinedOutputIdents) { + /* + * We assume that a permutation of a given input qubit Q at index i + * is performed in the circuit if an entry in both in the .output + * as well as the .input definition using the same literal is found + * with the input literal being defined at position i in the .input + * definition. If no such matching is found, we assume that the output + * is marked as garbage and thus remove the entry from the output + * permutation. + * + * The outputPermutation map will use be structured as shown in the + * documentation + * (https://mqt.readthedocs.io/projects/core/en/latest/quickstart.html#layout-information) + * with the output qubit being used as the key while the input qubit + * serves as the map entries value. + */ + if (!userDefinedInputIdents.count(outputIoIdent)) { + /* + * In case no matching input definition exists for a given output + * ident, remove said output qubit from the output permutation only if + * the output qubit is marked as garbage. If we would not take the + * garbage status into account, we would also remove ancillary output + * qubits which could potentially not be garbage qubits from the + * output permutation. + * + */ + if (logicalQubitIsGarbage(outputIoQubit)) + outputPermutation.erase(outputIoQubit); + } else if (const qc::Qubit matchingInputQubitForOutputLiteral = + userDefinedInputIdents.at(outputIoIdent); + matchingInputQubitForOutputLiteral != outputIoQubit) { + /* + * Only if the matching entries where defined at different indices + in their respective IO declaration + * do we update the existing 1-1 mapping for the given output qubit + */ + outputPermutation.insert_or_assign( + outputIoQubit, matchingInputQubitForOutputLiteral); + } + } } else if (cmd == ".VERSION" || cmd == ".INPUTBUS" || cmd == ".OUTPUTBUS") { is.ignore(std::numeric_limits::max(), '\n'); continue; @@ -480,4 +501,4 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, break; } } -} \ No newline at end of file +} From 28c82f2aba77df4f7ec9226e473fd0121a0ebaa0 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 26 Jun 2024 23:20:50 +0200 Subject: [PATCH 05/34] clang-tidy fixes --- src/parsers/RealParser.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 61008542e..36e61854d 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -12,9 +12,12 @@ #include #include #include +#include #include #include #include +#include +#include #include std::optional getQubitForVariableIdentFromAnyLookup( @@ -40,22 +43,24 @@ bool isValidIoName(const std::string_view& ioName) { return !ioName.empty() && std::all_of( ioName.cbegin(), ioName.cend(), [](const char ioNameCharacter) { - return std::isalnum(ioNameCharacter) || ioNameCharacter == '_'; + return static_cast(std::isalnum( + static_cast(ioNameCharacter))) || + ioNameCharacter == '_'; }); } std::unordered_map -parseIoNames(std::size_t lineInRealFileDefiningIoNames, - std::size_t expectedNumberOfIos, +parseIoNames(const std::size_t lineInRealFileDefiningIoNames, + const std::size_t expectedNumberOfIos, const std::string& ioNameIdentsRawValues) { std::unordered_map foundIoNames; std::size_t ioNameStartIdx = 0; - std::size_t ioNameEndIdx; + std::size_t ioNameEndIdx = 0; std::size_t ioIdx = 0; while (ioNameStartIdx < ioNameIdentsRawValues.size() && foundIoNames.size() <= expectedNumberOfIos) { - bool searchingForWhitespaceCharacter = + const bool searchingForWhitespaceCharacter = ioNameIdentsRawValues.at(ioNameStartIdx) != '"'; if (searchingForWhitespaceCharacter) ioNameEndIdx = ioNameIdentsRawValues.find_first_of(' ', ioNameStartIdx); @@ -72,13 +77,14 @@ parseIoNames(std::size_t lineInRealFileDefiningIoNames, std::to_string(ioIdx)); } } else { - ioNameEndIdx += !searchingForWhitespaceCharacter; + ioNameEndIdx += + static_cast(!searchingForWhitespaceCharacter); } std::size_t ioNameLength = ioNameEndIdx - ioNameStartIdx; // On windows the line ending could be the character sequence \r\n while on // linux system it would only be \n - if (ioNameLength && + if (ioNameLength > 0 && ioNameIdentsRawValues.at( std::min(ioNameEndIdx, ioNameIdentsRawValues.size() - 1)) == '\r') --ioNameLength; @@ -92,7 +98,7 @@ parseIoNames(std::size_t lineInRealFileDefiningIoNames, " invalid io name: " + ioName); } - ioNameStartIdx = ioNameEndIdx + searchingForWhitespaceCharacter; + ioNameStartIdx = ioNameEndIdx + static_cast(searchingForWhitespaceCharacter); /* * We offer the user the use of some special literals to denote either * constant inputs or garbage outputs instead of finding unique names for From 11892337cb0990056819817b37c8a15c3ac038e6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 21:26:23 +0000 Subject: [PATCH 06/34] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parsers/RealParser.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 36e61854d..b58bc1ac4 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -98,7 +98,8 @@ parseIoNames(const std::size_t lineInRealFileDefiningIoNames, " invalid io name: " + ioName); } - ioNameStartIdx = ioNameEndIdx + static_cast(searchingForWhitespaceCharacter); + ioNameStartIdx = ioNameEndIdx + + static_cast(searchingForWhitespaceCharacter); /* * We offer the user the use of some special literals to denote either * constant inputs or garbage outputs instead of finding unique names for @@ -190,7 +191,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { ancillary.resize(nqubits); garbage.resize(nqubits); } - + /* * TODO: Check whether more than the declared number of variables was * defined From 7d626b6f454f0d1d108508d37f5cfee9c376f1ed Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Sun, 7 Jul 2024 14:42:06 +0200 Subject: [PATCH 07/34] Intermediate commit to check changes for parsing of SWAP and iSWAP gates --- src/parsers/RealParser.cpp | 70 +++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index b58bc1ac4..9be252357 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -407,7 +407,7 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, const fp lambda = m.str(3).empty() ? static_cast(0L) : static_cast(std::stold(m.str(3))); - if (gate == V || gate == Vdg || m.str(1) == "c" || gate == SWAP) { + if (gate == V || gate == Vdg || m.str(1) == "c") { ncontrols = 1; } else if (gate == Peres || gate == Peresdg) { ncontrols = 2; @@ -427,6 +427,23 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, std::vector controls{}; std::istringstream iss(qubits); + const unsigned long numberOfGateLines = std::stoul(m.str(2)); + // Current parser implementation defines number of expected control lines + // (nControl) as nLines (of gate definition) - 1. Controlled swap gate has + // at most two target lines so we define the number of control lines as + // nLines - 2. + if (gate == SWAP) { + if (numberOfGateLines < 2) { + throw QFRException("[real parser] l: " + std::to_string(line) + + "msg: SWAP gate is expected to operate on at least " + "two qubits but only " + + std::to_string(ncontrols) + " were defined"); + } + ncontrols = numberOfGateLines - 2; + if (ncontrols) + gate = iSWAP; + } + // get controls and target for (std::size_t i = 0; i < ncontrols; ++i) { if (!(iss >> label)) { @@ -452,19 +469,24 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, : Control::Type::Pos); } - if (!(iss >> label)) { - throw QFRException("[real parser] l:" + std::to_string(line) + - " msg: Too few variables (no target) for gate " + - m.str(1)); - } - - // Since variable qubits can either be data or ancillary qubits our search - // will have to be conducted in both lookups - const std::optional targetLineQubit = - getQubitForVariableIdentFromAnyLookup(label, qregs, ancregs); - if (!targetLineQubit) { - throw QFRException("[real parser] l:" + std::to_string(line) + - " msg: Label " + label + " not found!"); + const unsigned long numberOfTargetLines = numberOfGateLines - ncontrols; + std::vector targetLineQubits(numberOfTargetLines, qc::Qubit()); + for (std::size_t i = 0; i < numberOfTargetLines; ++i) { + if (!(iss >> label)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Too few variables (no target) for gate " + + m.str(1)); + } + // Since variable qubits can either be data or ancillary qubits our search + // will have to be conducted in both lookups + if (const std::optional targetLineQubit = + getQubitForVariableIdentFromAnyLookup(label, qregs, ancregs); + targetLineQubit.has_value()) { + targetLineQubits[i] = *targetLineQubit; + } else { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Label " + label + " not found!"); + } } switch (gate) { @@ -479,28 +501,36 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, case V: case Vdg: emplace_back( - Controls{controls.cbegin(), controls.cend()}, *targetLineQubit, gate); + Controls{controls.cbegin(), controls.cend()}, + targetLineQubits.front(), gate); break; case X: - mcx(Controls{controls.cbegin(), controls.cend()}, *targetLineQubit); + mcx(Controls{controls.cbegin(), controls.cend()}, targetLineQubits.front()); break; case RX: case RY: case RZ: case P: emplace_back( - Controls{controls.cbegin(), controls.cend()}, *targetLineQubit, gate, + Controls{controls.cbegin(), controls.cend()}, + targetLineQubits.front(), gate, std::vector{PI / (lambda)}); break; case SWAP: + case iSWAP: + emplace_back( + Controls{controls.cbegin(), controls.cend()}, + Targets{targetLineQubits.cbegin(), targetLineQubits.cend()}, + gate + ); + break; case Peres: - case Peresdg: - case iSWAP: { + case Peresdg: { const auto target1 = controls.back().qubit; controls.pop_back(); emplace_back( Controls{controls.cbegin(), controls.cend()}, target1, - *targetLineQubit, gate); + targetLineQubits.front(), gate); break; } default: From e01c4686369c4d66d2d276117ebeab36d1213bf8 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Sun, 7 Jul 2024 20:00:16 +0200 Subject: [PATCH 08/34] Minor code-formatting as well as clang-tidy fixes --- src/parsers/RealParser.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 9be252357..0f85bc9ec 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include std::optional getQubitForVariableIdentFromAnyLookup( @@ -133,7 +134,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { * to avoid potential errors due to any future refactoring of said type, we * use an std::unordered_map instead */ - std::unordered_map userDefinedInputIdents; + std::unordered_map userDefinedInputIdents; while (true) { if (!static_cast(is >> cmd)) { @@ -204,7 +205,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } else if (cmd == ".CONSTANTS") { is >> std::ws; for (std::size_t i = 0; i < nclassics; ++i) { - char readConstantFlagValue; + char readConstantFlagValue = '-'; if (!is.get(readConstantFlagValue)) { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Failed read in '.constants' line"); @@ -213,7 +214,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { if (const bool isCurrentQubitMarkedAsAncillary = readConstantFlagValue == '0' || readConstantFlagValue == '1'; isCurrentQubitMarkedAsAncillary) { - const Qubit ancillaryQubit = static_cast(i); + const auto& ancillaryQubit = static_cast(i); setLogicalQubitAncillary(ancillaryQubit); /* @@ -237,7 +238,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } else if (cmd == ".GARBAGE") { is >> std::ws; for (std::size_t i = 0; i < nclassics; ++i) { - char readGarbageStatusFlagValue; + char readGarbageStatusFlagValue = '-'; if (!is.get(readGarbageStatusFlagValue)) { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Failed read in '.garbage' line"); @@ -262,7 +263,8 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { std::getline(is, ioNameIdentsLine); userDefinedInputIdents = - parseIoNames(line, expectedNumInputIos, ioNameIdentsLine); + parseIoNames(static_cast(line), expectedNumInputIos, + ioNameIdentsLine); if (userDefinedInputIdents.size() != expectedNumInputIos) { std::cout << userDefinedInputIdents.size() << "\n"; @@ -278,7 +280,8 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { std::getline(is, ioNameIdentsLine); const std::unordered_map userDefinedOutputIdents = - parseIoNames(line, expectedNumOutputIos, ioNameIdentsLine); + parseIoNames(static_cast(line), expectedNumOutputIos, + ioNameIdentsLine); if (userDefinedOutputIdents.size() != expectedNumOutputIos) { std::cout << userDefinedOutputIdents.size() << "\n"; @@ -304,7 +307,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { * with the output qubit being used as the key while the input qubit * serves as the map entries value. */ - if (!userDefinedInputIdents.count(outputIoIdent)) { + if (userDefinedInputIdents.count(outputIoIdent) == 0) { /* * In case no matching input definition exists for a given output * ident, remove said output qubit from the output permutation only if @@ -440,7 +443,7 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, std::to_string(ncontrols) + " were defined"); } ncontrols = numberOfGateLines - 2; - if (ncontrols) + if (ncontrols > 0) gate = iSWAP; } @@ -505,7 +508,8 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, targetLineQubits.front(), gate); break; case X: - mcx(Controls{controls.cbegin(), controls.cend()}, targetLineQubits.front()); + mcx(Controls{controls.cbegin(), controls.cend()}, + targetLineQubits.front()); break; case RX: case RY: @@ -513,16 +517,13 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, case P: emplace_back( Controls{controls.cbegin(), controls.cend()}, - targetLineQubits.front(), gate, - std::vector{PI / (lambda)}); + targetLineQubits.front(), gate, std::vector{PI / (lambda)}); break; case SWAP: case iSWAP: emplace_back( - Controls{controls.cbegin(), controls.cend()}, - Targets{targetLineQubits.cbegin(), targetLineQubits.cend()}, - gate - ); + Controls{controls.cbegin(), controls.cend()}, + Targets{targetLineQubits.cbegin(), targetLineQubits.cend()}, gate); break; case Peres: case Peresdg: { From 7b62565778d8f503ed3895ea4609b41ae73d180d Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Sun, 7 Jul 2024 22:14:26 +0200 Subject: [PATCH 09/34] Switched back operation type for controlled swap gates from iSWAP to SWAP. Small changes due to clang-tidy checks. --- src/parsers/RealParser.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 0f85bc9ec..d1e52e52e 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -430,7 +430,13 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, std::vector controls{}; std::istringstream iss(qubits); - const unsigned long numberOfGateLines = std::stoul(m.str(2)); + // TODO: Check how non-default RevLib .real specification gate types shall be supported + // i.e. c a b (which does not define the number of gate lines) + const std::string& stringifiedNumberOfGateLines = m.str(2); + const auto numberOfGateLines = + stringifiedNumberOfGateLines.empty() + ? 0 + : std::stoul(stringifiedNumberOfGateLines, nullptr, 0); // Current parser implementation defines number of expected control lines // (nControl) as nLines (of gate definition) - 1. Controlled swap gate has // at most two target lines so we define the number of control lines as @@ -443,8 +449,6 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, std::to_string(ncontrols) + " were defined"); } ncontrols = numberOfGateLines - 2; - if (ncontrols > 0) - gate = iSWAP; } // get controls and target @@ -461,19 +465,20 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, // Since variable qubits can either be data or ancillary qubits our search // will have to be conducted in both lookups - const std::optional controlLineQubit = - getQubitForVariableIdentFromAnyLookup(label, qregs, ancregs); - if (!controlLineQubit) { + if (const std::optional controlLineQubit = + getQubitForVariableIdentFromAnyLookup(label, qregs, ancregs); + controlLineQubit.has_value()) { + controls.emplace_back(*controlLineQubit, negativeControl + ? Control::Type::Neg + : Control::Type::Pos); + } else { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Label " + label + " not found!"); } - controls.emplace_back(*controlLineQubit, negativeControl - ? Control::Type::Neg - : Control::Type::Pos); } - const unsigned long numberOfTargetLines = numberOfGateLines - ncontrols; - std::vector targetLineQubits(numberOfTargetLines, qc::Qubit()); + const auto numberOfTargetLines = numberOfGateLines - ncontrols; + std::vector targetLineQubits(numberOfTargetLines, Qubit()); for (std::size_t i = 0; i < numberOfTargetLines; ++i) { if (!(iss >> label)) { throw QFRException("[real parser] l:" + std::to_string(line) + @@ -482,7 +487,7 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, } // Since variable qubits can either be data or ancillary qubits our search // will have to be conducted in both lookups - if (const std::optional targetLineQubit = + if (const std::optional targetLineQubit = getQubitForVariableIdentFromAnyLookup(label, qregs, ancregs); targetLineQubit.has_value()) { targetLineQubits[i] = *targetLineQubit; From 4712a70a3b751b1584dfefd09d396b52c7bd3dc4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 7 Jul 2024 20:14:49 +0000 Subject: [PATCH 10/34] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parsers/RealParser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index d1e52e52e..5dcfb7c63 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -430,8 +430,8 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, std::vector controls{}; std::istringstream iss(qubits); - // TODO: Check how non-default RevLib .real specification gate types shall be supported - // i.e. c a b (which does not define the number of gate lines) + // TODO: Check how non-default RevLib .real specification gate types shall + // be supported i.e. c a b (which does not define the number of gate lines) const std::string& stringifiedNumberOfGateLines = m.str(2); const auto numberOfGateLines = stringifiedNumberOfGateLines.empty() From 2f4c89a80842859bc6e616d28e04b2e3f32dfe12 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Tue, 16 Jul 2024 23:49:33 +0200 Subject: [PATCH 11/34] Bugfix for IO ident name parsing and validation. Added tests for real_parser. --- src/parsers/RealParser.cpp | 24 +- test/CMakeLists.txt | 1 + test/unittests/test_real_parser.cpp | 814 ++++++++++++++++++++++++++++ 3 files changed, 826 insertions(+), 13 deletions(-) create mode 100644 test/unittests/test_real_parser.cpp diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 5dcfb7c63..3808e276f 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -66,7 +66,8 @@ parseIoNames(const std::size_t lineInRealFileDefiningIoNames, if (searchingForWhitespaceCharacter) ioNameEndIdx = ioNameIdentsRawValues.find_first_of(' ', ioNameStartIdx); else - ioNameEndIdx = ioNameIdentsRawValues.find_first_of('"', ioNameStartIdx); + ioNameEndIdx = + ioNameIdentsRawValues.find_first_of('"', ioNameStartIdx + 1); if (ioNameEndIdx == std::string::npos) { ioNameEndIdx = ioNameIdentsRawValues.size(); @@ -90,17 +91,22 @@ parseIoNames(const std::size_t lineInRealFileDefiningIoNames, std::min(ioNameEndIdx, ioNameIdentsRawValues.size() - 1)) == '\r') --ioNameLength; - const std::string ioName = + const auto& ioName = ioNameIdentsRawValues.substr(ioNameStartIdx, ioNameLength); - if (!isValidIoName(ioName)) { + std::string_view ioNameToValidate = ioName; + if (!searchingForWhitespaceCharacter) { + ioNameToValidate = + ioNameToValidate.substr(1, ioNameToValidate.size() - 2); + } + + if (!isValidIoName(ioNameToValidate)) { throw qc::QFRException( "[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + " invalid io name: " + ioName); } - ioNameStartIdx = ioNameEndIdx + - static_cast(searchingForWhitespaceCharacter); + ioNameStartIdx = ioNameEndIdx + 1; /* * We offer the user the use of some special literals to denote either * constant inputs or garbage outputs instead of finding unique names for @@ -158,12 +164,6 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { " msg: Invalid file header"); } - /* - * TODO: Parsing currently assumes that expected entries are defined only - * once and in the expected definition order as defined in the .real header - * specification. (https://www.revlib.org/doc/docu/revlib_2_0_1.pdf - * Chapter 3.2) - */ if (cmd == ".BEGIN") { // header read complete return line; @@ -267,7 +267,6 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { ioNameIdentsLine); if (userDefinedInputIdents.size() != expectedNumInputIos) { - std::cout << userDefinedInputIdents.size() << "\n"; throw QFRException( "[real parser} l: " + std::to_string(line) + "msg: Expected " + std::to_string(expectedNumInputIos) + " inputs to be declared!"); @@ -284,7 +283,6 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { ioNameIdentsLine); if (userDefinedOutputIdents.size() != expectedNumOutputIos) { - std::cout << userDefinedOutputIdents.size() << "\n"; throw QFRException( "[real parser} l: " + std::to_string(line) + "msg: Expected " + std::to_string(expectedNumOutputIos) + " outputs to be declared!"); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1dfda8271..32c7dbad9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,7 @@ package_add_test( unittests/test_qfr_functionality.cpp unittests/test_symbolic.cpp unittests/test_qasm3_parser.cpp + unittests/test_real_parser.cpp test_operation.cpp) file(GLOB_RECURSE circuit_optimizer_tests "unittests/circuit_optimizer/*.cpp") diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp new file mode 100644 index 000000000..98e3581fc --- /dev/null +++ b/test/unittests/test_real_parser.cpp @@ -0,0 +1,814 @@ +#include "QuantumComputation.hpp" + +#include "gmock/gmock-matchers.h" +#include + +using namespace qc; +using ::testing::NotNull; + +class RealParserTest : public testing::Test { +public: + RealParserTest& UsingVersion(double versionNumber) { + realFileContent << REAL_HEADER_VERSION << " " << std::fixed + << std::setprecision(1) << versionNumber << "\n"; + return *this; + } + + RealParserTest& UsingNVariables(std::size_t numVariables) { + realFileContent << REAL_HEADER_NUMVARS << " " + << std::to_string(numVariables) << "\n"; + return *this; + } + + RealParserTest& UsingVariables( + const std::initializer_list& variableIdents) { + realFileContent << REAL_HEADER_VARIABLES; + for (const auto& variableIdent : variableIdents) + realFileContent << " " << variableIdent; + + realFileContent << "\n"; + return *this; + } + + RealParserTest& + UsingInputs(const std::initializer_list& inputIdents) { + realFileContent << REAL_HEADER_INPUTS; + for (const auto& inputIdent : inputIdents) + realFileContent << " " << inputIdent; + + realFileContent << "\n"; + return *this; + } + + RealParserTest& + UsingOutputs(const std::initializer_list& outputIdents) { + realFileContent << REAL_HEADER_OUTPUTS; + for (const auto& outputIdent : outputIdents) + realFileContent << " " << outputIdent; + + realFileContent << "\n"; + return *this; + } + + RealParserTest& + WithConstants(const std::initializer_list& constantValuePerVariable) { + realFileContent << REAL_HEADER_CONSTANTS << " "; + for (const auto& constantValue : constantValuePerVariable) + realFileContent << constantValue; + + realFileContent << "\n"; + return *this; + } + + RealParserTest& WithGarbageValues( + const std::initializer_list& isGarbageValuePerVariable) { + realFileContent << REAL_HEADER_GARBAGE << " "; + for (const auto& garbageValue : isGarbageValuePerVariable) + realFileContent << garbageValue; + + realFileContent << "\n"; + return *this; + } + + RealParserTest& WithEmptyGateList() { + realFileContent << REAL_HEADER_GATE_LIST_PREFIX << "\n" + << REAL_HEADER_GATE_LIST_POSTFIX; + return *this; + } + + RealParserTest& WithGates( + const std::initializer_list& stringifiedGateList) { + if (stringifiedGateList.size() == 0) + return WithEmptyGateList(); + + realFileContent << REAL_HEADER_GATE_LIST_PREFIX << "\n"; + for (const auto& stringifiedGate : stringifiedGateList) + realFileContent << stringifiedGate << "\n"; + + realFileContent << REAL_HEADER_GATE_LIST_POSTFIX; + return *this; + } + +protected: + const std::string REAL_HEADER_VERSION = ".version"; + const std::string REAL_HEADER_NUMVARS = ".numvars"; + const std::string REAL_HEADER_VARIABLES = ".variables"; + const std::string REAL_HEADER_INPUTS = ".inputs"; + const std::string REAL_HEADER_OUTPUTS = ".outputs"; + const std::string REAL_HEADER_CONSTANTS = ".constants"; + const std::string REAL_HEADER_GARBAGE = ".garbage"; + const std::string REAL_HEADER_GATE_LIST_PREFIX = ".begin"; + const std::string REAL_HEADER_GATE_LIST_POSTFIX = ".end"; + + static constexpr double DEFAULT_REAL_VERSION = 2.0; + + const char CONSTANT_VALUE_ZERO = '0'; + const char CONSTANT_VALUE_ONE = '1'; + const char CONSTANT_VALUE_NONE = '-'; + + const char IS_GARBAGE_STATE = '1'; + const char IS_NOT_GARBAGE_STATE = '-'; + + enum class GateType { Toffoli }; + + std::unique_ptr quantumComputationInstance; + std::stringstream realFileContent; + + void SetUp() override { + quantumComputationInstance = std::make_unique(); + ASSERT_THAT(quantumComputationInstance, NotNull()); + } + + static Permutation GetIdentityPermutation(std::size_t nQubits) { + auto identityPermutation = Permutation(); + for (std::size_t i = 0; i < nQubits; ++i) { + const auto qubit = static_cast(i); + identityPermutation.insert({i, i}); + } + return identityPermutation; + } + + static std::string stringifyGateType(const GateType gateType) { + switch (gateType) { + case GateType::Toffoli: + return "t"; + + default: + throw new std::invalid_argument("Failed to stringify gate type"); + } + } + + static std::string + stringifyGate(const GateType gateType, + const std::initializer_list& controlLines, + const std::initializer_list& targetLines) { + return stringifyGate(gateType, std::nullopt, controlLines, targetLines); + } + + static std::string + stringifyGate(const GateType gateType, + const std::optional& optionalNumberOfControlLines, + const std::initializer_list& controlLines, + const std::initializer_list& targetLines) { + std::stringstream stringifiedGateBuffer; + if (!controlLines.size() && !optionalNumberOfControlLines.has_value()) + stringifiedGateBuffer << stringifyGateType(gateType); + else + stringifiedGateBuffer + << stringifyGateType(gateType) + << std::to_string( + optionalNumberOfControlLines.value_or(controlLines.size())); + + for (const auto& controlLine : controlLines) + stringifiedGateBuffer << " " << controlLine; + + for (const auto& targetLine : targetLines) + stringifiedGateBuffer << " " << targetLine; + + return stringifiedGateBuffer.str(); + } +}; + +// TODO: Gate list prefix and postfix missing + +// ERROR TESTS +TEST_F(RealParserTest, MoreVariablesThanNumVariablesDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2", "v3"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, MoreInputsThanVariablesDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingInputs({"i1", "i2", "i3"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, MoreOutputsThanVariablesDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingOutputs({"o1", "o2", "o3"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, MoreConstantsThanVariablesDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .WithConstants( + {CONSTANT_VALUE_ZERO, CONSTANT_VALUE_ZERO, CONSTANT_VALUE_ZERO}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, MoreGarbageEntriesThanVariablesDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .WithGarbageValues( + {IS_GARBAGE_STATE, IS_NOT_GARBAGE_STATE, IS_GARBAGE_STATE}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, LessVariablesThanNumVariablesDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, LessInputsThanNumVariablesDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingInputs({"i1"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, LessOutputsThanNumVariablesDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingOutputs({"o1"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, LessConstantsThanNumVariablesDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .WithConstants({CONSTANT_VALUE_NONE}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, LessGarbageEntriesThanNumVariablesDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .WithGarbageValues({IS_NOT_GARBAGE_STATE}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, InvalidVariableIdentDeclaration) { GTEST_SKIP(); } +TEST_F(RealParserTest, InvalidInputIdentDeclaration) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingInputs({"test-input1", "i2"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, InvalidInputIdentDeclarationInQuote) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingInputs({"\"test-input1\"", "i2"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, InvalidOutputIdentDeclaration) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingOutputs({"i1", "test-output1"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, InvalidOutputIdentDeclarationInQuote) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingInputs({"\"test-output1\"", "o2"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, InputIdentMatchingVariableIdentIsNotAllowed) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingInputs({"i1", "v2"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, OutputIdentMatchingVariableIdentIsNotAllowed) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingOutputs({"v1", "o2"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateVariableIdentDeclaration) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v1"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateInputIdentDeclaration) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingInputs({"i1", "i1"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateOutputIdentDeclaration) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingOutputs({"o1", "o1"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, InvalidConstantStateValue) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .WithConstants({CONSTANT_VALUE_ONE, 't'}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, InvalidGarbageStateValue) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .WithGarbageValues({'t', IS_NOT_GARBAGE_STATE}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, GateWithMoreLinesThanDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(3) + .UsingVariables({"v1", "v2", "v3"}) + .WithGates({stringifyGate(GateType::Toffoli, std::optional(2), + {"v1", "v2"}, {"v3"})}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, GateWithLessLinesThanDeclared) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(3) + .UsingVariables({"v1", "v2", "v3"}) + .WithGates( + {stringifyGate(GateType::Toffoli, std::optional(3), {"v1"}, {"v3"})}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, GateWithControlLineTargetingUnknownVariable) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .WithGates({stringifyGate(GateType::Toffoli, {"v3"}, {"v2"})}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, GateWithTargetLineTargetingUnknownVariable) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v3"})}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +// OK TESTS +TEST_F(RealParserTest, ConstantValueZero) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .WithConstants({CONSTANT_VALUE_ZERO, CONSTANT_VALUE_NONE}) + .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + stringifyGate(GateType::Toffoli, {"v2"}, {"v1"})}); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(1, quantumComputationInstance->getNancillae()); + ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(false, false)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(true, false)); + + ASSERT_EQ( + std::hash{}(GetIdentityPermutation(2)), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +TEST_F(RealParserTest, ConstantValueOne) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .WithConstants({CONSTANT_VALUE_NONE, CONSTANT_VALUE_ONE}) + .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + stringifyGate(GateType::Toffoli, {"v2"}, {"v1"})}); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(1, quantumComputationInstance->getNancillae()); + ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(false, false)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(false, true)); + + ASSERT_EQ( + std::hash{}(GetIdentityPermutation(2)), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +TEST_F(RealParserTest, GarbageValues) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .WithGarbageValues({IS_NOT_GARBAGE_STATE, IS_GARBAGE_STATE}) + .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + stringifyGate(GateType::Toffoli, {"v2"}, {"v1"})}); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(1, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(false, true)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(false, false)); + + ASSERT_EQ( + std::hash{}(GetIdentityPermutation(2)), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +TEST_F(RealParserTest, InputIdentDeclarationInQuotes) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingInputs({"i1", "\"test_input_1\""}) + .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + stringifyGate(GateType::Toffoli, {"v2"}, {"v1"})}); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(false, false)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(false, false)); + + ASSERT_EQ( + std::hash{}(GetIdentityPermutation(2)), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +TEST_F(RealParserTest, OutputIdentDeclarationInQuotes) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingOutputs({"\"other_output_2\"", "\"o2\""}) + .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + stringifyGate(GateType::Toffoli, {"v2"}, {"v1"})}); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(false, false)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(false, false)); + + ASSERT_EQ( + std::hash{}(GetIdentityPermutation(2)), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +TEST_F(RealParserTest, + InputIdentInQuotesAndMatchingOutputNotInQuotesNotConsideredEqual) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(4) + .UsingVariables({"v1", "v2", "v3", "v4"}) + .UsingInputs({"i1", "\"o2\"", "i3", "\"o4\""}) + .UsingOutputs({"o1", "o2", "o3", "o4"}) + .WithGates({ + stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), + stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), + stringifyGate(GateType::Toffoli, {"v4"}, {"v3"}), + }); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(false, false)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(false, false)); + + ASSERT_EQ( + std::hash{}(GetIdentityPermutation(2)), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +TEST_F(RealParserTest, + InputIdentNotInQuotesAndMatchingOutputInQuotesNotConsideredEqual) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(4) + .UsingVariables({"v1", "v2", "v3", "v4"}) + .UsingInputs({"i1", "i2", "i3", "i4"}) + .UsingOutputs({"o1", "\"i1\"", "o2", "\"i4\""}) + .WithGates({ + stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), + stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), + stringifyGate(GateType::Toffoli, {"v4"}, {"v3"}), + }); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(false, false)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(false, false)); + + ASSERT_EQ( + std::hash{}(GetIdentityPermutation(2)), + std::hash{}(quantumComputationInstance->outputPermutation)); +} +TEST_F(RealParserTest, MatchingInputAndOutputNotInQuotes) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(4) + .UsingVariables({"v1", "v2", "v3", "v4"}) + .UsingInputs({"i1", "i2", "i3", "i4"}) + .WithConstants({CONSTANT_VALUE_ONE, CONSTANT_VALUE_NONE, + CONSTANT_VALUE_NONE, CONSTANT_VALUE_ZERO}) + .UsingOutputs({"o1", "i1", "i4", "o2"}) + .WithGarbageValues({IS_GARBAGE_STATE, IS_NOT_GARBAGE_STATE, + IS_NOT_GARBAGE_STATE, IS_GARBAGE_STATE}) + .WithGates({ + stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), + stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), + stringifyGate(GateType::Toffoli, {"v4"}, {"v3"}), + }); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(4, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(true, false, false, true)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(true, false, false, true)); + + Permutation expectedOutputPermutation; + expectedOutputPermutation.emplace(static_cast(2), + static_cast(1)); + + expectedOutputPermutation.emplace(static_cast(3), + static_cast(4)); + + ASSERT_EQ( + std::hash{}(expectedOutputPermutation), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +/* + * TODO: What if no matching output for a given input exists and the original inputs qubit was transformed to another position. + * Should we remove the output permutation entry of the output qubit (only if the original input qubit was moved). + * + * TODO: Garbage state will be reset and recreated based on idle state of qubit in QuantumComputation::initializeIOMapping L227-248 + */ +TEST_F(RealParserTest, MatchingInputAndOutputInQuotes) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(4) + .UsingVariables({"v1", "v2", "v3", "v4"}) + .UsingInputs({"i1", "\"i2\"", "\"i3\"", "i4"}) + .WithConstants({CONSTANT_VALUE_NONE, CONSTANT_VALUE_ONE, + CONSTANT_VALUE_ZERO, CONSTANT_VALUE_NONE}) + .UsingOutputs({"i4", "\"i3\"", "\"i2\"", "o1"}) + .WithGarbageValues({IS_GARBAGE_STATE, IS_NOT_GARBAGE_STATE, + IS_NOT_GARBAGE_STATE, IS_GARBAGE_STATE}) + .WithGates({ + stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), + stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), + stringifyGate(GateType::Toffoli, {"v4"}, {"v3"}), + }); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(4, quantumComputationInstance->getNqubits()); + ASSERT_EQ(2, quantumComputationInstance->getNancillae()); + ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(true, false, false, true)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(false, true, true, false)); + + Permutation expectedOutputPermutation; + expectedOutputPermutation.emplace(static_cast(1), + static_cast(4)); + + expectedOutputPermutation.emplace(static_cast(2), + static_cast(3)); + + expectedOutputPermutation.emplace(static_cast(3), + static_cast(2)); + + ASSERT_EQ( + std::hash{}(expectedOutputPermutation), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +TEST_F(RealParserTest, + OutputPermutationCorrectlySetBetweenMatchingInputAndOutputEntries) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(4) + .UsingVariables({"v1", "v2", "v3", "v4"}) + .UsingInputs({"i1", "i2", "i3", "i4"}) + .UsingOutputs({"\"i4\"", "i3", "\"i2\"", "i1"}) + .WithGates({ + stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), + stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), + stringifyGate(GateType::Toffoli, {"v4"}, {"v3"}), + }); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(4, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(false, false, false, false)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(false, false, false, false)); + + Permutation expectedOutputPermutation; + expectedOutputPermutation.emplace(static_cast(4), + static_cast(1)); + + expectedOutputPermutation.emplace(static_cast(3), + static_cast(2)); + + ASSERT_EQ( + std::hash{}(expectedOutputPermutation), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +TEST_F(RealParserTest, OutputPermutationForGarbageQubitsNotCreated) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(4) + .UsingVariables({"v1", "v2", "v3", "v4"}) + .UsingInputs({"i1", "i2", "i3", "i4"}) + .UsingOutputs({"i4", "o1", "o2", "i1"}) + .WithGarbageValues({IS_GARBAGE_STATE, IS_NOT_GARBAGE_STATE, + IS_NOT_GARBAGE_STATE, IS_GARBAGE_STATE}) + .WithGates({ + stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), + stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), + stringifyGate(GateType::Toffoli, {"v4"}, {"v3"}), + }); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(4, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(true, false, false, true)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(false, false, false, false)); + + Permutation expectedOutputPermutation; + expectedOutputPermutation.emplace(static_cast(2), + static_cast(2)); + + expectedOutputPermutation.emplace(static_cast(3), + static_cast(3)); + + ASSERT_EQ( + std::hash{}(expectedOutputPermutation), + std::hash{}(quantumComputationInstance->outputPermutation)); +} From 197075fd07dbe1479000d8384f3b0db7eadcd636 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 17 Jul 2024 17:26:18 +0200 Subject: [PATCH 12/34] Updated validation of number of defined .constants as well as .garbage entries. Added validation that no IO with ident equal to any variable ident is defined and no duplicate variable ident is defined. --- src/parsers/RealParser.cpp | 125 +++++++++++++++++++++++++++---------- 1 file changed, 91 insertions(+), 34 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 3808e276f..5c98f5050 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -53,15 +53,17 @@ bool isValidIoName(const std::string_view& ioName) { std::unordered_map parseIoNames(const std::size_t lineInRealFileDefiningIoNames, const std::size_t expectedNumberOfIos, - const std::string& ioNameIdentsRawValues) { + const std::string& ioNameIdentsRawValues, + const std::unordered_set& variableIdentLookup) { std::unordered_map foundIoNames; std::size_t ioNameStartIdx = 0; std::size_t ioNameEndIdx = 0; std::size_t ioIdx = 0; + bool searchingForWhitespaceCharacter = false; while (ioNameStartIdx < ioNameIdentsRawValues.size() && foundIoNames.size() <= expectedNumberOfIos) { - const bool searchingForWhitespaceCharacter = + searchingForWhitespaceCharacter = ioNameIdentsRawValues.at(ioNameStartIdx) != '"'; if (searchingForWhitespaceCharacter) ioNameEndIdx = ioNameIdentsRawValues.find_first_of(' ', ioNameStartIdx); @@ -103,7 +105,14 @@ parseIoNames(const std::size_t lineInRealFileDefiningIoNames, if (!isValidIoName(ioNameToValidate)) { throw qc::QFRException( "[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + - " invalid io name: " + ioName); + " msg: invalid io name: " + ioName); + } + + if (variableIdentLookup.count(ioName) > 0) { + throw qc::QFRException( + "[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + + " msg: IO ident matched already declared variable with name " + + ioName); } ioNameStartIdx = ioNameEndIdx + 1; @@ -118,10 +127,23 @@ parseIoNames(const std::size_t lineInRealFileDefiningIoNames, !ioNameInsertionIntoLookupResult.second) { throw qc::QFRException("[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + - "duplicate io name: " + ioName); + " msg: duplicate io name: " + ioName); } } } + + if (searchingForWhitespaceCharacter && + ioNameEndIdx + 1 < ioNameIdentsRawValues.size() && + ioNameIdentsRawValues.at(ioNameEndIdx + 1) == ' ') { + throw qc::QFRException( + "[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + + " msg: expected only " + std::to_string(expectedNumberOfIos) + + " io identifiers to be declared but io identifier delimiter was found" + " after " + + std::to_string(expectedNumberOfIos) + + " identifiers were detected (which we assume will be followed by " + "another io identifier)!"); + } return foundIoNames; } @@ -141,6 +163,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { * use an std::unordered_map instead */ std::unordered_map userDefinedInputIdents; + std::unordered_set userDeclaredVariableIdents; while (true) { if (!static_cast(is >> cmd)) { @@ -178,12 +201,25 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } nclassics = nqubits; } else if (cmd == ".VARIABLES") { + userDeclaredVariableIdents.reserve(nclassics); for (std::size_t i = 0; i < nclassics; ++i) { if (!static_cast(is >> variable) || variable.at(0) == '.') { throw QFRException( "[real parser] l:" + std::to_string(line) + " msg: Invalid or insufficient variables declared"); } + + if (!isValidIoName(variable)) { + throw qc::QFRException("[real parser] l: " + std::to_string(line) + + " msg: invalid variable name: " + variable); + } + + if (userDeclaredVariableIdents.count(variable) > 0) { + throw qc::QFRException("[real parser] l: " + std::to_string(line) + + " msg: duplicate variable name: " + variable); + } + userDeclaredVariableIdents.emplace(variable); + const auto qubit = static_cast(i); qregs.insert({variable, {qubit, 1U}}); cregs.insert({"c_" + variable, {qubit, 1U}}); @@ -194,8 +230,6 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } /* - * TODO: Check whether more than the declared number of variables was - * defined * * TODO: Ancillary qubits are expected to be initialized with the value * '0' but .real file .constants definition allows the two values '0' and @@ -204,17 +238,26 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { */ } else if (cmd == ".CONSTANTS") { is >> std::ws; - for (std::size_t i = 0; i < nclassics; ++i) { - char readConstantFlagValue = '-'; - if (!is.get(readConstantFlagValue)) { - throw QFRException("[real parser] l:" + std::to_string(line) + - " msg: Failed read in '.constants' line"); - } + std::string constantsValuePerIoDefinition; + if (!std::getline(is, constantsValuePerIoDefinition)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in '.constants' line"); + } + + if (constantsValuePerIoDefinition.size() != nclassics) { + throw QFRException("[real parser] l: " + std::to_string(line) + + " msg: Expected " + std::to_string(nclassics) + + " constant values but " + + std::to_string(constantsValuePerIoDefinition.size()) + + " were declared!"); + } + std::size_t constantValueIdx = 0; + for (const auto constantValuePerIo : constantsValuePerIoDefinition) { if (const bool isCurrentQubitMarkedAsAncillary = - readConstantFlagValue == '0' || readConstantFlagValue == '1'; + constantValuePerIo == '0' || constantValuePerIo == '1'; isCurrentQubitMarkedAsAncillary) { - const auto& ancillaryQubit = static_cast(i); + const auto& ancillaryQubit = static_cast(constantValueIdx); setLogicalQubitAncillary(ancillaryQubit); /* @@ -228,43 +271,54 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { ancregs.insert_or_assign( associatedVariableNameForQubitRegister, qc::QuantumRegister(std::make_pair(ancillaryQubit, 1U))); - } else if (readConstantFlagValue != '-') { + } else if (constantValuePerIo != '-') { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Invalid value in '.constants' header: '" + - std::to_string(readConstantFlagValue) + "'"); + std::to_string(constantValuePerIo) + "'"); } + ++constantValueIdx; } - is.ignore(std::numeric_limits::max(), '\n'); } else if (cmd == ".GARBAGE") { is >> std::ws; - for (std::size_t i = 0; i < nclassics; ++i) { - char readGarbageStatusFlagValue = '-'; - if (!is.get(readGarbageStatusFlagValue)) { - throw QFRException("[real parser] l:" + std::to_string(line) + - " msg: Failed read in '.garbage' line"); - } + std::string garbageStatePerIoDefinition; + if (!std::getline(is, garbageStatePerIoDefinition)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in '.garbage' line"); + } - if (const bool isCurrentQubitMarkedAsGarbage = - readGarbageStatusFlagValue == '1'; + if (garbageStatePerIoDefinition.size() != nclassics) { + throw QFRException( + "[real parser] l: " + std::to_string(line) + " msg: Expected " + + std::to_string(nclassics) + " garbage state values but " + + std::to_string(garbageStatePerIoDefinition.size()) + + " were declared!"); + } + + std::size_t garbageStateIdx = 0; + for (const auto garbageStateValue : garbageStatePerIoDefinition) { + if (const bool isCurrentQubitMarkedAsGarbage = garbageStateValue == '1'; isCurrentQubitMarkedAsGarbage) { - setLogicalQubitGarbage(static_cast(i)); - } else if (readGarbageStatusFlagValue != '-') { + setLogicalQubitGarbage(static_cast(garbageStateIdx)); + } else if (garbageStateValue != '-') { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Invalid value in '.garbage' header: '" + - std::to_string(readGarbageStatusFlagValue) + "'"); + std::to_string(garbageStateValue) + "'"); } + garbageStateIdx++; } - is.ignore(std::numeric_limits::max(), '\n'); } else if (cmd == ".INPUTS") { // .INPUT: specifies initial layout is >> std::ws; const std::size_t expectedNumInputIos = nclassics; std::string ioNameIdentsLine; - std::getline(is, ioNameIdentsLine); - + if (!std::getline(is, ioNameIdentsLine)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in '.inputs' line"); + } + userDefinedInputIdents = parseIoNames(static_cast(line), expectedNumInputIos, - ioNameIdentsLine); + ioNameIdentsLine, userDeclaredVariableIdents); if (userDefinedInputIdents.size() != expectedNumInputIos) { throw QFRException( @@ -276,11 +330,14 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { is >> std::ws; const std::size_t expectedNumOutputIos = nclassics; std::string ioNameIdentsLine; - std::getline(is, ioNameIdentsLine); + if (!std::getline(is, ioNameIdentsLine)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in '.outputs' line"); + } const std::unordered_map userDefinedOutputIdents = parseIoNames(static_cast(line), expectedNumOutputIos, - ioNameIdentsLine); + ioNameIdentsLine, userDeclaredVariableIdents); if (userDefinedOutputIdents.size() != expectedNumOutputIos) { throw QFRException( From 5a114e9e50f6d4b7de0243e8ee088aaa1442e002 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 17 Jul 2024 17:33:37 +0200 Subject: [PATCH 13/34] Number of gate lines will now be sum of control and target lines for stringified gates created in test --- test/unittests/test_real_parser.cpp | 87 ++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index 98e3581fc..3c8d6bcb9 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -147,17 +147,21 @@ class RealParserTest : public testing::Test { static std::string stringifyGate(const GateType gateType, - const std::optional& optionalNumberOfControlLines, + const std::optional& optionalNumberOfGateLines, const std::initializer_list& controlLines, const std::initializer_list& targetLines) { + EXPECT_TRUE(controlLines.size() >= static_cast(0) && + targetLines.size() > static_cast(0)) + << "Gate must have at least one line defined"; + std::stringstream stringifiedGateBuffer; - if (!controlLines.size() && !optionalNumberOfControlLines.has_value()) + if (!controlLines.size() && !optionalNumberOfGateLines.has_value()) stringifiedGateBuffer << stringifyGateType(gateType); else stringifiedGateBuffer << stringifyGateType(gateType) << std::to_string( - optionalNumberOfControlLines.value_or(controlLines.size())); + optionalNumberOfGateLines.value_or(controlLines.size() + targetLines.size())); for (const auto& controlLine : controlLines) stringifiedGateBuffer << " " << controlLine; @@ -292,7 +296,17 @@ TEST_F(RealParserTest, LessGarbageEntriesThanNumVariablesDeclared) { QFRException); } -TEST_F(RealParserTest, InvalidVariableIdentDeclaration) { GTEST_SKIP(); } +TEST_F(RealParserTest, InvalidVariableIdentDeclaration) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"variable-1", "v2"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + TEST_F(RealParserTest, InvalidInputIdentDeclaration) { UsingVersion(DEFAULT_REAL_VERSION) .UsingNVariables(2) @@ -400,6 +414,30 @@ TEST_F(RealParserTest, DuplicateOutputIdentDeclaration) { QFRException); } +TEST_F(RealParserTest, MissingClosingQuoteInIoIdentifierDoesNotLeadToInfinityLoop) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingOutputs({"\"o1", "o1"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, MissingOpeningQuoteInIoIdentifierIsDetectedAsFaulty) { + UsingVersion(DEFAULT_REAL_VERSION) + .UsingNVariables(2) + .UsingVariables({"v1", "v2"}) + .UsingOutputs({"o1\"", "o1"}) + .WithEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + TEST_F(RealParserTest, InvalidConstantStateValue) { UsingVersion(DEFAULT_REAL_VERSION) .UsingNVariables(2) @@ -537,8 +575,12 @@ TEST_F(RealParserTest, GarbageValues) { ASSERT_THAT(quantumComputationInstance->ancillary, testing::ElementsAre(false, false)); + Permutation expectedOutputPermutation; + expectedOutputPermutation.emplace(static_cast(0), + static_cast(0)); + ASSERT_EQ( - std::hash{}(GetIdentityPermutation(2)), + std::hash{}(expectedOutputPermutation), std::hash{}(quantumComputationInstance->outputPermutation)); } @@ -607,16 +649,16 @@ TEST_F(RealParserTest, EXPECT_NO_THROW( quantumComputationInstance->import(realFileContent, Format::Real)); - ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(4, quantumComputationInstance->getNqubits()); ASSERT_EQ(0, quantumComputationInstance->getNancillae()); ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); ASSERT_THAT(quantumComputationInstance->garbage, - testing::ElementsAre(false, false)); + testing::ElementsAre(false, false, false, false)); ASSERT_THAT(quantumComputationInstance->ancillary, - testing::ElementsAre(false, false)); + testing::ElementsAre(false, false, false, false)); ASSERT_EQ( - std::hash{}(GetIdentityPermutation(2)), + std::hash{}(GetIdentityPermutation(4)), std::hash{}(quantumComputationInstance->outputPermutation)); } @@ -637,18 +679,19 @@ TEST_F(RealParserTest, EXPECT_NO_THROW( quantumComputationInstance->import(realFileContent, Format::Real)); - ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(4, quantumComputationInstance->getNqubits()); ASSERT_EQ(0, quantumComputationInstance->getNancillae()); ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); ASSERT_THAT(quantumComputationInstance->garbage, - testing::ElementsAre(false, false)); + testing::ElementsAre(false, false, false, false)); ASSERT_THAT(quantumComputationInstance->ancillary, - testing::ElementsAre(false, false)); + testing::ElementsAre(false, false, false, false)); ASSERT_EQ( - std::hash{}(GetIdentityPermutation(2)), + std::hash{}(GetIdentityPermutation(4)), std::hash{}(quantumComputationInstance->outputPermutation)); } + TEST_F(RealParserTest, MatchingInputAndOutputNotInQuotes) { UsingVersion(DEFAULT_REAL_VERSION) .UsingNVariables(4) @@ -670,7 +713,7 @@ TEST_F(RealParserTest, MatchingInputAndOutputNotInQuotes) { quantumComputationInstance->import(realFileContent, Format::Real)); ASSERT_EQ(4, quantumComputationInstance->getNqubits()); - ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(2, quantumComputationInstance->getNancillae()); ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); ASSERT_THAT(quantumComputationInstance->garbage, testing::ElementsAre(true, false, false, true)); @@ -694,6 +737,8 @@ TEST_F(RealParserTest, MatchingInputAndOutputNotInQuotes) { * Should we remove the output permutation entry of the output qubit (only if the original input qubit was moved). * * TODO: Garbage state will be reset and recreated based on idle state of qubit in QuantumComputation::initializeIOMapping L227-248 + * + * TODO: Extend checks for whether more than N qubit garbage or constant state have been declared */ TEST_F(RealParserTest, MatchingInputAndOutputInQuotes) { UsingVersion(DEFAULT_REAL_VERSION) @@ -757,17 +802,17 @@ TEST_F(RealParserTest, ASSERT_EQ(4, quantumComputationInstance->getNqubits()); ASSERT_EQ(0, quantumComputationInstance->getNancillae()); - ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); + ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); ASSERT_THAT(quantumComputationInstance->garbage, testing::ElementsAre(false, false, false, false)); ASSERT_THAT(quantumComputationInstance->ancillary, testing::ElementsAre(false, false, false, false)); Permutation expectedOutputPermutation; - expectedOutputPermutation.emplace(static_cast(4), - static_cast(1)); - expectedOutputPermutation.emplace(static_cast(3), + static_cast(0)); + + expectedOutputPermutation.emplace(static_cast(1), static_cast(2)); ASSERT_EQ( @@ -802,12 +847,12 @@ TEST_F(RealParserTest, OutputPermutationForGarbageQubitsNotCreated) { testing::ElementsAre(false, false, false, false)); Permutation expectedOutputPermutation; + expectedOutputPermutation.emplace(static_cast(1), + static_cast(1)); + expectedOutputPermutation.emplace(static_cast(2), static_cast(2)); - expectedOutputPermutation.emplace(static_cast(3), - static_cast(3)); - ASSERT_EQ( std::hash{}(expectedOutputPermutation), std::hash{}(quantumComputationInstance->outputPermutation)); From c8c405705748bd0f426c74cd2d8ce7f24d1df8e5 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 17 Jul 2024 17:54:28 +0200 Subject: [PATCH 14/34] Added missing addition of NOT gate if constant with value '1' was declared. --- src/parsers/RealParser.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 5c98f5050..a9169590e 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -258,6 +258,12 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { constantValuePerIo == '0' || constantValuePerIo == '1'; isCurrentQubitMarkedAsAncillary) { const auto& ancillaryQubit = static_cast(constantValueIdx); + // Since ancillary qubits are assumed to have an initial value of + // zero, we need to add an inversion gate to derive the correct + // initial value of 1. + if (constantValuePerIo == '1') + x(ancillaryQubit); + setLogicalQubitAncillary(ancillaryQubit); /* From 78d286ad908dd39a821fad9d4af74d94d44b4b04 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 17 Jul 2024 20:33:32 +0200 Subject: [PATCH 15/34] clang-tidy fixes and removal of TODO comments --- src/parsers/RealParser.cpp | 25 +++++++++++++------------ test/unittests/test_real_parser.cpp | 23 +++++++++-------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index a9169590e..ff432c300 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -210,12 +211,12 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } if (!isValidIoName(variable)) { - throw qc::QFRException("[real parser] l: " + std::to_string(line) + + throw QFRException("[real parser] l: " + std::to_string(line) + " msg: invalid variable name: " + variable); } if (userDeclaredVariableIdents.count(variable) > 0) { - throw qc::QFRException("[real parser] l: " + std::to_string(line) + + throw QFRException("[real parser] l: " + std::to_string(line) + " msg: duplicate variable name: " + variable); } userDeclaredVariableIdents.emplace(variable); @@ -245,11 +246,11 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } if (constantsValuePerIoDefinition.size() != nclassics) { - throw QFRException("[real parser] l: " + std::to_string(line) + - " msg: Expected " + std::to_string(nclassics) + - " constant values but " + + throw QFRException( + "[real parser] l: " + std::to_string(line) + " msg: Expected " + + std::to_string(nclassics) + " constant values but " + std::to_string(constantsValuePerIoDefinition.size()) + - " were declared!"); + " were declared!"); } std::size_t constantValueIdx = 0; @@ -293,11 +294,11 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } if (garbageStatePerIoDefinition.size() != nclassics) { - throw QFRException( - "[real parser] l: " + std::to_string(line) + " msg: Expected " + - std::to_string(nclassics) + " garbage state values but " + - std::to_string(garbageStatePerIoDefinition.size()) + - " were declared!"); + throw QFRException("[real parser] l: " + std::to_string(line) + + " msg: Expected " + std::to_string(nclassics) + + " garbage state values but " + + std::to_string(garbageStatePerIoDefinition.size()) + + " were declared!"); } std::size_t garbageStateIdx = 0; @@ -321,7 +322,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Failed read in '.inputs' line"); } - + userDefinedInputIdents = parseIoNames(static_cast(line), expectedNumInputIos, ioNameIdentsLine, userDeclaredVariableIdents); diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index 3c8d6bcb9..c1123d491 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -1,8 +1,12 @@ +#include "Definitions.hpp" +#include "Permutation.hpp" #include "QuantumComputation.hpp" #include "gmock/gmock-matchers.h" #include +#include + using namespace qc; using ::testing::NotNull; @@ -123,7 +127,7 @@ class RealParserTest : public testing::Test { auto identityPermutation = Permutation(); for (std::size_t i = 0; i < nQubits; ++i) { const auto qubit = static_cast(i); - identityPermutation.insert({i, i}); + identityPermutation.insert({qubit, qubit}); } return identityPermutation; } @@ -160,8 +164,8 @@ class RealParserTest : public testing::Test { else stringifiedGateBuffer << stringifyGateType(gateType) - << std::to_string( - optionalNumberOfGateLines.value_or(controlLines.size() + targetLines.size())); + << std::to_string(optionalNumberOfGateLines.value_or( + controlLines.size() + targetLines.size())); for (const auto& controlLine : controlLines) stringifiedGateBuffer << " " << controlLine; @@ -173,8 +177,6 @@ class RealParserTest : public testing::Test { } }; -// TODO: Gate list prefix and postfix missing - // ERROR TESTS TEST_F(RealParserTest, MoreVariablesThanNumVariablesDeclared) { UsingVersion(DEFAULT_REAL_VERSION) @@ -414,7 +416,8 @@ TEST_F(RealParserTest, DuplicateOutputIdentDeclaration) { QFRException); } -TEST_F(RealParserTest, MissingClosingQuoteInIoIdentifierDoesNotLeadToInfinityLoop) { +TEST_F(RealParserTest, + MissingClosingQuoteInIoIdentifierDoesNotLeadToInfinityLoop) { UsingVersion(DEFAULT_REAL_VERSION) .UsingNVariables(2) .UsingVariables({"v1", "v2"}) @@ -732,14 +735,6 @@ TEST_F(RealParserTest, MatchingInputAndOutputNotInQuotes) { std::hash{}(quantumComputationInstance->outputPermutation)); } -/* - * TODO: What if no matching output for a given input exists and the original inputs qubit was transformed to another position. - * Should we remove the output permutation entry of the output qubit (only if the original input qubit was moved). - * - * TODO: Garbage state will be reset and recreated based on idle state of qubit in QuantumComputation::initializeIOMapping L227-248 - * - * TODO: Extend checks for whether more than N qubit garbage or constant state have been declared - */ TEST_F(RealParserTest, MatchingInputAndOutputInQuotes) { UsingVersion(DEFAULT_REAL_VERSION) .UsingNVariables(4) From dc8d5915cc9e4db9ca71fc05ce433c8779e70f83 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 17 Jul 2024 18:34:15 +0000 Subject: [PATCH 16/34] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parsers/RealParser.cpp | 4 ++-- test/unittests/test_real_parser.cpp | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index ff432c300..6694c5fa5 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -212,12 +212,12 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { if (!isValidIoName(variable)) { throw QFRException("[real parser] l: " + std::to_string(line) + - " msg: invalid variable name: " + variable); + " msg: invalid variable name: " + variable); } if (userDeclaredVariableIdents.count(variable) > 0) { throw QFRException("[real parser] l: " + std::to_string(line) + - " msg: duplicate variable name: " + variable); + " msg: duplicate variable name: " + variable); } userDeclaredVariableIdents.emplace(variable); diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index c1123d491..84bdac478 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -3,9 +3,8 @@ #include "QuantumComputation.hpp" #include "gmock/gmock-matchers.h" -#include - #include +#include using namespace qc; using ::testing::NotNull; From 2898165c70a91e544238d979d37dd077ce7ac7bf Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 17 Jul 2024 23:35:11 +0200 Subject: [PATCH 17/34] clang-tidy fixes for test_real_parser.cpp --- test/unittests/test_real_parser.cpp | 512 ++++++++++++++-------------- 1 file changed, 258 insertions(+), 254 deletions(-) diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index 84bdac478..3890443f2 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -3,29 +3,39 @@ #include "QuantumComputation.hpp" #include "gmock/gmock-matchers.h" +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include using namespace qc; using ::testing::NotNull; class RealParserTest : public testing::Test { public: - RealParserTest& UsingVersion(double versionNumber) { - realFileContent << REAL_HEADER_VERSION << " " << std::fixed + RealParserTest& usingVersion(double versionNumber) { + realFileContent << realHeaderVersionCommandPrefix << " " << std::fixed << std::setprecision(1) << versionNumber << "\n"; return *this; } - RealParserTest& UsingNVariables(std::size_t numVariables) { - realFileContent << REAL_HEADER_NUMVARS << " " + RealParserTest& usingNVariables(std::size_t numVariables) { + realFileContent << realHeaderNumVarsCommandPrefix << " " << std::to_string(numVariables) << "\n"; return *this; } - RealParserTest& UsingVariables( + RealParserTest& usingVariables( const std::initializer_list& variableIdents) { - realFileContent << REAL_HEADER_VARIABLES; + realFileContent << realHeaderVariablesCommandPrefix; for (const auto& variableIdent : variableIdents) realFileContent << " " << variableIdent; @@ -34,8 +44,8 @@ class RealParserTest : public testing::Test { } RealParserTest& - UsingInputs(const std::initializer_list& inputIdents) { - realFileContent << REAL_HEADER_INPUTS; + usingInputs(const std::initializer_list& inputIdents) { + realFileContent << realHeaderInputCommandPrefix; for (const auto& inputIdent : inputIdents) realFileContent << " " << inputIdent; @@ -44,8 +54,8 @@ class RealParserTest : public testing::Test { } RealParserTest& - UsingOutputs(const std::initializer_list& outputIdents) { - realFileContent << REAL_HEADER_OUTPUTS; + usingOutputs(const std::initializer_list& outputIdents) { + realFileContent << realHeaderOutputCommandPrefix; for (const auto& outputIdent : outputIdents) realFileContent << " " << outputIdent; @@ -54,8 +64,8 @@ class RealParserTest : public testing::Test { } RealParserTest& - WithConstants(const std::initializer_list& constantValuePerVariable) { - realFileContent << REAL_HEADER_CONSTANTS << " "; + withConstants(const std::initializer_list& constantValuePerVariable) { + realFileContent << realHeaderConstantsCommandPrefix << " "; for (const auto& constantValue : constantValuePerVariable) realFileContent << constantValue; @@ -63,9 +73,9 @@ class RealParserTest : public testing::Test { return *this; } - RealParserTest& WithGarbageValues( + RealParserTest& withGarbageValues( const std::initializer_list& isGarbageValuePerVariable) { - realFileContent << REAL_HEADER_GARBAGE << " "; + realFileContent << realHeaderGarbageCommandPrefix << " "; for (const auto& garbageValue : isGarbageValuePerVariable) realFileContent << garbageValue; @@ -73,44 +83,44 @@ class RealParserTest : public testing::Test { return *this; } - RealParserTest& WithEmptyGateList() { - realFileContent << REAL_HEADER_GATE_LIST_PREFIX << "\n" - << REAL_HEADER_GATE_LIST_POSTFIX; + RealParserTest& withEmptyGateList() { + realFileContent << realHeaderGateListPrefix << "\n" + << reakHeaderGateListPostfix; return *this; } - RealParserTest& WithGates( + RealParserTest& withGates( const std::initializer_list& stringifiedGateList) { if (stringifiedGateList.size() == 0) - return WithEmptyGateList(); + return withEmptyGateList(); - realFileContent << REAL_HEADER_GATE_LIST_PREFIX << "\n"; + realFileContent << realHeaderGateListPrefix << "\n"; for (const auto& stringifiedGate : stringifiedGateList) realFileContent << stringifiedGate << "\n"; - realFileContent << REAL_HEADER_GATE_LIST_POSTFIX; + realFileContent << reakHeaderGateListPostfix; return *this; } protected: - const std::string REAL_HEADER_VERSION = ".version"; - const std::string REAL_HEADER_NUMVARS = ".numvars"; - const std::string REAL_HEADER_VARIABLES = ".variables"; - const std::string REAL_HEADER_INPUTS = ".inputs"; - const std::string REAL_HEADER_OUTPUTS = ".outputs"; - const std::string REAL_HEADER_CONSTANTS = ".constants"; - const std::string REAL_HEADER_GARBAGE = ".garbage"; - const std::string REAL_HEADER_GATE_LIST_PREFIX = ".begin"; - const std::string REAL_HEADER_GATE_LIST_POSTFIX = ".end"; + const std::string realHeaderVersionCommandPrefix = ".version"; + const std::string realHeaderNumVarsCommandPrefix = ".numvars"; + const std::string realHeaderVariablesCommandPrefix = ".variables"; + const std::string realHeaderInputCommandPrefix = ".inputs"; + const std::string realHeaderOutputCommandPrefix = ".outputs"; + const std::string realHeaderConstantsCommandPrefix = ".constants"; + const std::string realHeaderGarbageCommandPrefix = ".garbage"; + const std::string realHeaderGateListPrefix = ".begin"; + const std::string reakHeaderGateListPostfix = ".end"; static constexpr double DEFAULT_REAL_VERSION = 2.0; - const char CONSTANT_VALUE_ZERO = '0'; - const char CONSTANT_VALUE_ONE = '1'; - const char CONSTANT_VALUE_NONE = '-'; + const char constantValueZero = '0'; + const char constantValueOne = '1'; + const char constantValueNone = '-'; - const char IS_GARBAGE_STATE = '1'; - const char IS_NOT_GARBAGE_STATE = '-'; + const char isGarbageState = '1'; + const char isNotGarbageState = '-'; enum class GateType { Toffoli }; @@ -122,7 +132,7 @@ class RealParserTest : public testing::Test { ASSERT_THAT(quantumComputationInstance, NotNull()); } - static Permutation GetIdentityPermutation(std::size_t nQubits) { + static Permutation getIdentityPermutation(std::size_t nQubits) { auto identityPermutation = Permutation(); for (std::size_t i = 0; i < nQubits; ++i) { const auto qubit = static_cast(i); @@ -132,13 +142,10 @@ class RealParserTest : public testing::Test { } static std::string stringifyGateType(const GateType gateType) { - switch (gateType) { - case GateType::Toffoli: + if (gateType == GateType::Toffoli) return "t"; - default: - throw new std::invalid_argument("Failed to stringify gate type"); - } + throw std::invalid_argument("Failed to stringify gate type"); } static std::string @@ -153,12 +160,11 @@ class RealParserTest : public testing::Test { const std::optional& optionalNumberOfGateLines, const std::initializer_list& controlLines, const std::initializer_list& targetLines) { - EXPECT_TRUE(controlLines.size() >= static_cast(0) && - targetLines.size() > static_cast(0)) + EXPECT_TRUE(targetLines.size() > static_cast(0)) << "Gate must have at least one line defined"; std::stringstream stringifiedGateBuffer; - if (!controlLines.size() && !optionalNumberOfGateLines.has_value()) + if (controlLines.size() == 0 && !optionalNumberOfGateLines.has_value()) stringifiedGateBuffer << stringifyGateType(gateType); else stringifiedGateBuffer @@ -178,10 +184,10 @@ class RealParserTest : public testing::Test { // ERROR TESTS TEST_F(RealParserTest, MoreVariablesThanNumVariablesDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2", "v3"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2", "v3"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -189,11 +195,11 @@ TEST_F(RealParserTest, MoreVariablesThanNumVariablesDeclared) { } TEST_F(RealParserTest, MoreInputsThanVariablesDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingInputs({"i1", "i2", "i3"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i1", "i2", "i3"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -201,11 +207,11 @@ TEST_F(RealParserTest, MoreInputsThanVariablesDeclared) { } TEST_F(RealParserTest, MoreOutputsThanVariablesDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingOutputs({"o1", "o2", "o3"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"o1", "o2", "o3"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -213,12 +219,11 @@ TEST_F(RealParserTest, MoreOutputsThanVariablesDeclared) { } TEST_F(RealParserTest, MoreConstantsThanVariablesDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .WithConstants( - {CONSTANT_VALUE_ZERO, CONSTANT_VALUE_ZERO, CONSTANT_VALUE_ZERO}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withConstants({constantValueZero, constantValueZero, constantValueZero}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -226,12 +231,11 @@ TEST_F(RealParserTest, MoreConstantsThanVariablesDeclared) { } TEST_F(RealParserTest, MoreGarbageEntriesThanVariablesDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .WithGarbageValues( - {IS_GARBAGE_STATE, IS_NOT_GARBAGE_STATE, IS_GARBAGE_STATE}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGarbageValues({isGarbageState, isNotGarbageState, isGarbageState}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -239,10 +243,10 @@ TEST_F(RealParserTest, MoreGarbageEntriesThanVariablesDeclared) { } TEST_F(RealParserTest, LessVariablesThanNumVariablesDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -250,11 +254,11 @@ TEST_F(RealParserTest, LessVariablesThanNumVariablesDeclared) { } TEST_F(RealParserTest, LessInputsThanNumVariablesDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingInputs({"i1"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i1"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -262,11 +266,11 @@ TEST_F(RealParserTest, LessInputsThanNumVariablesDeclared) { } TEST_F(RealParserTest, LessOutputsThanNumVariablesDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingOutputs({"o1"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"o1"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -274,11 +278,11 @@ TEST_F(RealParserTest, LessOutputsThanNumVariablesDeclared) { } TEST_F(RealParserTest, LessConstantsThanNumVariablesDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .WithConstants({CONSTANT_VALUE_NONE}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withConstants({constantValueNone}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -286,11 +290,11 @@ TEST_F(RealParserTest, LessConstantsThanNumVariablesDeclared) { } TEST_F(RealParserTest, LessGarbageEntriesThanNumVariablesDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .WithGarbageValues({IS_NOT_GARBAGE_STATE}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGarbageValues({isNotGarbageState}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -298,10 +302,10 @@ TEST_F(RealParserTest, LessGarbageEntriesThanNumVariablesDeclared) { } TEST_F(RealParserTest, InvalidVariableIdentDeclaration) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"variable-1", "v2"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"variable-1", "v2"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -309,11 +313,11 @@ TEST_F(RealParserTest, InvalidVariableIdentDeclaration) { } TEST_F(RealParserTest, InvalidInputIdentDeclaration) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingInputs({"test-input1", "i2"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"test-input1", "i2"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -321,11 +325,11 @@ TEST_F(RealParserTest, InvalidInputIdentDeclaration) { } TEST_F(RealParserTest, InvalidInputIdentDeclarationInQuote) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingInputs({"\"test-input1\"", "i2"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"\"test-input1\"", "i2"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -333,11 +337,11 @@ TEST_F(RealParserTest, InvalidInputIdentDeclarationInQuote) { } TEST_F(RealParserTest, InvalidOutputIdentDeclaration) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingOutputs({"i1", "test-output1"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"i1", "test-output1"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -345,11 +349,11 @@ TEST_F(RealParserTest, InvalidOutputIdentDeclaration) { } TEST_F(RealParserTest, InvalidOutputIdentDeclarationInQuote) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingInputs({"\"test-output1\"", "o2"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"\"test-output1\"", "o2"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -357,11 +361,11 @@ TEST_F(RealParserTest, InvalidOutputIdentDeclarationInQuote) { } TEST_F(RealParserTest, InputIdentMatchingVariableIdentIsNotAllowed) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingInputs({"i1", "v2"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i1", "v2"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -369,11 +373,11 @@ TEST_F(RealParserTest, InputIdentMatchingVariableIdentIsNotAllowed) { } TEST_F(RealParserTest, OutputIdentMatchingVariableIdentIsNotAllowed) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingOutputs({"v1", "o2"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"v1", "o2"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -381,10 +385,10 @@ TEST_F(RealParserTest, OutputIdentMatchingVariableIdentIsNotAllowed) { } TEST_F(RealParserTest, DuplicateVariableIdentDeclaration) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v1"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v1"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -392,11 +396,11 @@ TEST_F(RealParserTest, DuplicateVariableIdentDeclaration) { } TEST_F(RealParserTest, DuplicateInputIdentDeclaration) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingInputs({"i1", "i1"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i1", "i1"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -404,11 +408,11 @@ TEST_F(RealParserTest, DuplicateInputIdentDeclaration) { } TEST_F(RealParserTest, DuplicateOutputIdentDeclaration) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingOutputs({"o1", "o1"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"o1", "o1"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -417,11 +421,11 @@ TEST_F(RealParserTest, DuplicateOutputIdentDeclaration) { TEST_F(RealParserTest, MissingClosingQuoteInIoIdentifierDoesNotLeadToInfinityLoop) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingOutputs({"\"o1", "o1"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"\"o1", "o1"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -429,11 +433,11 @@ TEST_F(RealParserTest, } TEST_F(RealParserTest, MissingOpeningQuoteInIoIdentifierIsDetectedAsFaulty) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingOutputs({"o1\"", "o1"}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"o1\"", "o1"}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -441,11 +445,11 @@ TEST_F(RealParserTest, MissingOpeningQuoteInIoIdentifierIsDetectedAsFaulty) { } TEST_F(RealParserTest, InvalidConstantStateValue) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .WithConstants({CONSTANT_VALUE_ONE, 't'}) - .WithEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withConstants({constantValueOne, 't'}) + .withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -453,10 +457,10 @@ TEST_F(RealParserTest, InvalidConstantStateValue) { } TEST_F(RealParserTest, InvalidGarbageStateValue) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .WithGarbageValues({'t', IS_NOT_GARBAGE_STATE}); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGarbageValues({'t', isNotGarbageState}); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -464,10 +468,10 @@ TEST_F(RealParserTest, InvalidGarbageStateValue) { } TEST_F(RealParserTest, GateWithMoreLinesThanDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(3) - .UsingVariables({"v1", "v2", "v3"}) - .WithGates({stringifyGate(GateType::Toffoli, std::optional(2), + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(3) + .usingVariables({"v1", "v2", "v3"}) + .withGates({stringifyGate(GateType::Toffoli, std::optional(2), {"v1", "v2"}, {"v3"})}); EXPECT_THROW( @@ -476,10 +480,10 @@ TEST_F(RealParserTest, GateWithMoreLinesThanDeclared) { } TEST_F(RealParserTest, GateWithLessLinesThanDeclared) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(3) - .UsingVariables({"v1", "v2", "v3"}) - .WithGates( + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(3) + .usingVariables({"v1", "v2", "v3"}) + .withGates( {stringifyGate(GateType::Toffoli, std::optional(3), {"v1"}, {"v3"})}); EXPECT_THROW( @@ -488,10 +492,10 @@ TEST_F(RealParserTest, GateWithLessLinesThanDeclared) { } TEST_F(RealParserTest, GateWithControlLineTargetingUnknownVariable) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .WithGates({stringifyGate(GateType::Toffoli, {"v3"}, {"v2"})}); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGates({stringifyGate(GateType::Toffoli, {"v3"}, {"v2"})}); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -499,10 +503,10 @@ TEST_F(RealParserTest, GateWithControlLineTargetingUnknownVariable) { } TEST_F(RealParserTest, GateWithTargetLineTargetingUnknownVariable) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v3"})}); + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v3"})}); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -511,11 +515,11 @@ TEST_F(RealParserTest, GateWithTargetLineTargetingUnknownVariable) { // OK TESTS TEST_F(RealParserTest, ConstantValueZero) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .WithConstants({CONSTANT_VALUE_ZERO, CONSTANT_VALUE_NONE}) - .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withConstants({constantValueZero, constantValueNone}) + .withGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"})}); EXPECT_NO_THROW( @@ -530,16 +534,16 @@ TEST_F(RealParserTest, ConstantValueZero) { testing::ElementsAre(true, false)); ASSERT_EQ( - std::hash{}(GetIdentityPermutation(2)), + std::hash{}(getIdentityPermutation(2)), std::hash{}(quantumComputationInstance->outputPermutation)); } TEST_F(RealParserTest, ConstantValueOne) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .WithConstants({CONSTANT_VALUE_NONE, CONSTANT_VALUE_ONE}) - .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withConstants({constantValueNone, constantValueOne}) + .withGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"})}); EXPECT_NO_THROW( @@ -554,16 +558,16 @@ TEST_F(RealParserTest, ConstantValueOne) { testing::ElementsAre(false, true)); ASSERT_EQ( - std::hash{}(GetIdentityPermutation(2)), + std::hash{}(getIdentityPermutation(2)), std::hash{}(quantumComputationInstance->outputPermutation)); } TEST_F(RealParserTest, GarbageValues) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .WithGarbageValues({IS_NOT_GARBAGE_STATE, IS_GARBAGE_STATE}) - .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGarbageValues({isNotGarbageState, isGarbageState}) + .withGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"})}); EXPECT_NO_THROW( @@ -587,11 +591,11 @@ TEST_F(RealParserTest, GarbageValues) { } TEST_F(RealParserTest, InputIdentDeclarationInQuotes) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingInputs({"i1", "\"test_input_1\""}) - .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i1", "\"test_input_1\""}) + .withGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"})}); EXPECT_NO_THROW( @@ -606,16 +610,16 @@ TEST_F(RealParserTest, InputIdentDeclarationInQuotes) { testing::ElementsAre(false, false)); ASSERT_EQ( - std::hash{}(GetIdentityPermutation(2)), + std::hash{}(getIdentityPermutation(2)), std::hash{}(quantumComputationInstance->outputPermutation)); } TEST_F(RealParserTest, OutputIdentDeclarationInQuotes) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(2) - .UsingVariables({"v1", "v2"}) - .UsingOutputs({"\"other_output_2\"", "\"o2\""}) - .WithGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"\"other_output_2\"", "\"o2\""}) + .withGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"})}); EXPECT_NO_THROW( @@ -630,18 +634,18 @@ TEST_F(RealParserTest, OutputIdentDeclarationInQuotes) { testing::ElementsAre(false, false)); ASSERT_EQ( - std::hash{}(GetIdentityPermutation(2)), + std::hash{}(getIdentityPermutation(2)), std::hash{}(quantumComputationInstance->outputPermutation)); } TEST_F(RealParserTest, InputIdentInQuotesAndMatchingOutputNotInQuotesNotConsideredEqual) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(4) - .UsingVariables({"v1", "v2", "v3", "v4"}) - .UsingInputs({"i1", "\"o2\"", "i3", "\"o4\""}) - .UsingOutputs({"o1", "o2", "o3", "o4"}) - .WithGates({ + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(4) + .usingVariables({"v1", "v2", "v3", "v4"}) + .usingInputs({"i1", "\"o2\"", "i3", "\"o4\""}) + .usingOutputs({"o1", "o2", "o3", "o4"}) + .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), @@ -660,18 +664,18 @@ TEST_F(RealParserTest, testing::ElementsAre(false, false, false, false)); ASSERT_EQ( - std::hash{}(GetIdentityPermutation(4)), + std::hash{}(getIdentityPermutation(4)), std::hash{}(quantumComputationInstance->outputPermutation)); } TEST_F(RealParserTest, InputIdentNotInQuotesAndMatchingOutputInQuotesNotConsideredEqual) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(4) - .UsingVariables({"v1", "v2", "v3", "v4"}) - .UsingInputs({"i1", "i2", "i3", "i4"}) - .UsingOutputs({"o1", "\"i1\"", "o2", "\"i4\""}) - .WithGates({ + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(4) + .usingVariables({"v1", "v2", "v3", "v4"}) + .usingInputs({"i1", "i2", "i3", "i4"}) + .usingOutputs({"o1", "\"i1\"", "o2", "\"i4\""}) + .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), @@ -690,21 +694,21 @@ TEST_F(RealParserTest, testing::ElementsAre(false, false, false, false)); ASSERT_EQ( - std::hash{}(GetIdentityPermutation(4)), + std::hash{}(getIdentityPermutation(4)), std::hash{}(quantumComputationInstance->outputPermutation)); } TEST_F(RealParserTest, MatchingInputAndOutputNotInQuotes) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(4) - .UsingVariables({"v1", "v2", "v3", "v4"}) - .UsingInputs({"i1", "i2", "i3", "i4"}) - .WithConstants({CONSTANT_VALUE_ONE, CONSTANT_VALUE_NONE, - CONSTANT_VALUE_NONE, CONSTANT_VALUE_ZERO}) - .UsingOutputs({"o1", "i1", "i4", "o2"}) - .WithGarbageValues({IS_GARBAGE_STATE, IS_NOT_GARBAGE_STATE, - IS_NOT_GARBAGE_STATE, IS_GARBAGE_STATE}) - .WithGates({ + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(4) + .usingVariables({"v1", "v2", "v3", "v4"}) + .usingInputs({"i1", "i2", "i3", "i4"}) + .withConstants({constantValueOne, constantValueNone, constantValueNone, + constantValueZero}) + .usingOutputs({"o1", "i1", "i4", "o2"}) + .withGarbageValues({isGarbageState, isNotGarbageState, isNotGarbageState, + isGarbageState}) + .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), @@ -735,16 +739,16 @@ TEST_F(RealParserTest, MatchingInputAndOutputNotInQuotes) { } TEST_F(RealParserTest, MatchingInputAndOutputInQuotes) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(4) - .UsingVariables({"v1", "v2", "v3", "v4"}) - .UsingInputs({"i1", "\"i2\"", "\"i3\"", "i4"}) - .WithConstants({CONSTANT_VALUE_NONE, CONSTANT_VALUE_ONE, - CONSTANT_VALUE_ZERO, CONSTANT_VALUE_NONE}) - .UsingOutputs({"i4", "\"i3\"", "\"i2\"", "o1"}) - .WithGarbageValues({IS_GARBAGE_STATE, IS_NOT_GARBAGE_STATE, - IS_NOT_GARBAGE_STATE, IS_GARBAGE_STATE}) - .WithGates({ + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(4) + .usingVariables({"v1", "v2", "v3", "v4"}) + .usingInputs({"i1", "\"i2\"", "\"i3\"", "i4"}) + .withConstants({constantValueNone, constantValueOne, constantValueZero, + constantValueNone}) + .usingOutputs({"i4", "\"i3\"", "\"i2\"", "o1"}) + .withGarbageValues({isGarbageState, isNotGarbageState, isNotGarbageState, + isGarbageState}) + .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), @@ -779,12 +783,12 @@ TEST_F(RealParserTest, MatchingInputAndOutputInQuotes) { TEST_F(RealParserTest, OutputPermutationCorrectlySetBetweenMatchingInputAndOutputEntries) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(4) - .UsingVariables({"v1", "v2", "v3", "v4"}) - .UsingInputs({"i1", "i2", "i3", "i4"}) - .UsingOutputs({"\"i4\"", "i3", "\"i2\"", "i1"}) - .WithGates({ + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(4) + .usingVariables({"v1", "v2", "v3", "v4"}) + .usingInputs({"i1", "i2", "i3", "i4"}) + .usingOutputs({"\"i4\"", "i3", "\"i2\"", "i1"}) + .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), @@ -815,14 +819,14 @@ TEST_F(RealParserTest, } TEST_F(RealParserTest, OutputPermutationForGarbageQubitsNotCreated) { - UsingVersion(DEFAULT_REAL_VERSION) - .UsingNVariables(4) - .UsingVariables({"v1", "v2", "v3", "v4"}) - .UsingInputs({"i1", "i2", "i3", "i4"}) - .UsingOutputs({"i4", "o1", "o2", "i1"}) - .WithGarbageValues({IS_GARBAGE_STATE, IS_NOT_GARBAGE_STATE, - IS_NOT_GARBAGE_STATE, IS_GARBAGE_STATE}) - .WithGates({ + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(4) + .usingVariables({"v1", "v2", "v3", "v4"}) + .usingInputs({"i1", "i2", "i3", "i4"}) + .usingOutputs({"i4", "o1", "o2", "i1"}) + .withGarbageValues({isGarbageState, isNotGarbageState, isNotGarbageState, + isGarbageState}) + .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), stringifyGate(GateType::Toffoli, {"v3"}, {"v4"}), From c24a9d2bb359c155a79879b945a5e732d7a26b7f Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Sat, 27 Jul 2024 18:41:56 +0200 Subject: [PATCH 18/34] Added check that every output should have a matching input counterpart or must be marked as garbage. --- src/parsers/RealParser.cpp | 47 ++++++------ test/unittests/test_real_parser.cpp | 106 +++++++++++++++++++--------- 2 files changed, 101 insertions(+), 52 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 6694c5fa5..c6c253ba4 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -164,6 +164,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { * use an std::unordered_map instead */ std::unordered_map userDefinedInputIdents; + std::unordered_map userDefinedOutputIdents; std::unordered_set userDeclaredVariableIdents; while (true) { @@ -329,7 +330,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { if (userDefinedInputIdents.size() != expectedNumInputIos) { throw QFRException( - "[real parser} l: " + std::to_string(line) + "msg: Expected " + + "[real parser] l: " + std::to_string(line) + "msg: Expected " + std::to_string(expectedNumInputIos) + " inputs to be declared!"); } } else if (cmd == ".OUTPUTS") { @@ -342,26 +343,28 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { " msg: Failed read in '.outputs' line"); } - const std::unordered_map userDefinedOutputIdents = + userDefinedOutputIdents = parseIoNames(static_cast(line), expectedNumOutputIos, ioNameIdentsLine, userDeclaredVariableIdents); if (userDefinedOutputIdents.size() != expectedNumOutputIos) { throw QFRException( - "[real parser} l: " + std::to_string(line) + "msg: Expected " + + "[real parser] l: " + std::to_string(line) + "msg: Expected " + std::to_string(expectedNumOutputIos) + " outputs to be declared!"); } + if (userDefinedInputIdents.empty()) + continue; + for (const auto& [outputIoIdent, outputIoQubit] : userDefinedOutputIdents) { /* * We assume that a permutation of a given input qubit Q at index i * is performed in the circuit if an entry in both in the .output - * as well as the .input definition using the same literal is found + * as well as the .input definition using the same literal is found, * with the input literal being defined at position i in the .input - * definition. If no such matching is found, we assume that the output - * is marked as garbage and thus remove the entry from the output - * permutation. + * definition. If no such matching is found, we require that the output + * is marked as garbage. * * The outputPermutation map will use be structured as shown in the * documentation @@ -371,23 +374,27 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { */ if (userDefinedInputIdents.count(outputIoIdent) == 0) { /* - * In case no matching input definition exists for a given output - * ident, remove said output qubit from the output permutation only if - * the output qubit is marked as garbage. If we would not take the - * garbage status into account, we would also remove ancillary output - * qubits which could potentially not be garbage qubits from the - * output permutation. - * + * The current implementation requires that the .garbage definition is + * define prior to the .output one. */ - if (logicalQubitIsGarbage(outputIoQubit)) - outputPermutation.erase(outputIoQubit); - } else if (const qc::Qubit matchingInputQubitForOutputLiteral = + if (!logicalQubitIsGarbage(outputIoQubit)) { + throw QFRException("[real parser] l: " + std::to_string(line) + + " msg: outputs without matching inputs are " + "expected to be marked as garbage"); + } + } else if (const Qubit matchingInputQubitForOutputLiteral = userDefinedInputIdents.at(outputIoIdent); - matchingInputQubitForOutputLiteral != outputIoQubit) { + matchingInputQubitForOutputLiteral != outputIoQubit && + !logicalQubitIsGarbage(outputIoQubit)) { /* + * We do not need to check whether a mapping from one input to any + * output exists, since we require that the idents defined in either + * of the .input as well as the .output definition are unique in their + * definition. + * * Only if the matching entries where defined at different indices - in their respective IO declaration - * do we update the existing 1-1 mapping for the given output qubit + * in their respective IO declaration do we update the existing 1-1 + * mapping for the given output qubit */ outputPermutation.insert_or_assign( outputIoQubit, matchingInputQubitForOutputLiteral); diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index 3890443f2..49990f18a 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -336,6 +336,18 @@ TEST_F(RealParserTest, InvalidInputIdentDeclarationInQuote) { QFRException); } +TEST_F(RealParserTest, EmptyInputIdentInQuotesNotAllowed) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i1", "\"\""}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + TEST_F(RealParserTest, InvalidOutputIdentDeclaration) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) @@ -360,6 +372,18 @@ TEST_F(RealParserTest, InvalidOutputIdentDeclarationInQuote) { QFRException); } +TEST_F(RealParserTest, EmptyOutputIdentInQuotesNotAllowed) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"\"\"", "o2"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + TEST_F(RealParserTest, InputIdentMatchingVariableIdentIsNotAllowed) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) @@ -644,7 +668,9 @@ TEST_F(RealParserTest, .usingNVariables(4) .usingVariables({"v1", "v2", "v3", "v4"}) .usingInputs({"i1", "\"o2\"", "i3", "\"o4\""}) - .usingOutputs({"o1", "o2", "o3", "o4"}) + .withGarbageValues({isNotGarbageState, isGarbageState, isNotGarbageState, + isGarbageState}) + .usingOutputs({"i1", "o2", "i3", "o4"}) .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), @@ -657,14 +683,18 @@ TEST_F(RealParserTest, ASSERT_EQ(4, quantumComputationInstance->getNqubits()); ASSERT_EQ(0, quantumComputationInstance->getNancillae()); - ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); + ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); ASSERT_THAT(quantumComputationInstance->garbage, - testing::ElementsAre(false, false, false, false)); + testing::ElementsAre(false, true, false, true)); ASSERT_THAT(quantumComputationInstance->ancillary, testing::ElementsAre(false, false, false, false)); + auto expectedOutputPermutation = getIdentityPermutation(4); + expectedOutputPermutation.erase(1); + expectedOutputPermutation.erase(3); + ASSERT_EQ( - std::hash{}(getIdentityPermutation(4)), + std::hash{}(expectedOutputPermutation), std::hash{}(quantumComputationInstance->outputPermutation)); } @@ -674,7 +704,9 @@ TEST_F(RealParserTest, .usingNVariables(4) .usingVariables({"v1", "v2", "v3", "v4"}) .usingInputs({"i1", "i2", "i3", "i4"}) - .usingOutputs({"o1", "\"i1\"", "o2", "\"i4\""}) + .withGarbageValues({isNotGarbageState, isGarbageState, isNotGarbageState, + isGarbageState}) + .usingOutputs({"i1", "\"i1\"", "i2", "\"i4\""}) .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), @@ -687,14 +719,18 @@ TEST_F(RealParserTest, ASSERT_EQ(4, quantumComputationInstance->getNqubits()); ASSERT_EQ(0, quantumComputationInstance->getNancillae()); - ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); + ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); ASSERT_THAT(quantumComputationInstance->garbage, - testing::ElementsAre(false, false, false, false)); + testing::ElementsAre(false, true, false, true)); ASSERT_THAT(quantumComputationInstance->ancillary, testing::ElementsAre(false, false, false, false)); + auto expectedOutputPermutation = getIdentityPermutation(4); + expectedOutputPermutation.erase(1); + expectedOutputPermutation.erase(3); + ASSERT_EQ( - std::hash{}(getIdentityPermutation(4)), + std::hash{}(expectedOutputPermutation), std::hash{}(quantumComputationInstance->outputPermutation)); } @@ -705,9 +741,9 @@ TEST_F(RealParserTest, MatchingInputAndOutputNotInQuotes) { .usingInputs({"i1", "i2", "i3", "i4"}) .withConstants({constantValueOne, constantValueNone, constantValueNone, constantValueZero}) - .usingOutputs({"o1", "i1", "i4", "o2"}) .withGarbageValues({isGarbageState, isNotGarbageState, isNotGarbageState, isGarbageState}) + .usingOutputs({"o1", "i1", "i4", "o2"}) .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), @@ -727,11 +763,11 @@ TEST_F(RealParserTest, MatchingInputAndOutputNotInQuotes) { testing::ElementsAre(true, false, false, true)); Permutation expectedOutputPermutation; - expectedOutputPermutation.emplace(static_cast(2), - static_cast(1)); + expectedOutputPermutation.emplace(static_cast(1), + static_cast(0)); - expectedOutputPermutation.emplace(static_cast(3), - static_cast(4)); + expectedOutputPermutation.emplace(static_cast(2), + static_cast(3)); ASSERT_EQ( std::hash{}(expectedOutputPermutation), @@ -745,9 +781,9 @@ TEST_F(RealParserTest, MatchingInputAndOutputInQuotes) { .usingInputs({"i1", "\"i2\"", "\"i3\"", "i4"}) .withConstants({constantValueNone, constantValueOne, constantValueZero, constantValueNone}) - .usingOutputs({"i4", "\"i3\"", "\"i2\"", "o1"}) - .withGarbageValues({isGarbageState, isNotGarbageState, isNotGarbageState, + .withGarbageValues({isNotGarbageState, isNotGarbageState, isNotGarbageState, isGarbageState}) + .usingOutputs({"i4", "\"i3\"", "\"i2\"", "o1"}) .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), @@ -760,22 +796,22 @@ TEST_F(RealParserTest, MatchingInputAndOutputInQuotes) { ASSERT_EQ(4, quantumComputationInstance->getNqubits()); ASSERT_EQ(2, quantumComputationInstance->getNancillae()); - ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); + ASSERT_EQ(1, quantumComputationInstance->getNgarbageQubits()); ASSERT_THAT(quantumComputationInstance->garbage, - testing::ElementsAre(true, false, false, true)); + testing::ElementsAre(false, false, false, true)); ASSERT_THAT(quantumComputationInstance->ancillary, testing::ElementsAre(false, true, true, false)); Permutation expectedOutputPermutation; - expectedOutputPermutation.emplace(static_cast(1), - static_cast(4)); - - expectedOutputPermutation.emplace(static_cast(2), + expectedOutputPermutation.emplace(static_cast(0), static_cast(3)); - expectedOutputPermutation.emplace(static_cast(3), + expectedOutputPermutation.emplace(static_cast(1), static_cast(2)); + expectedOutputPermutation.emplace(static_cast(2), + static_cast(1)); + ASSERT_EQ( std::hash{}(expectedOutputPermutation), std::hash{}(quantumComputationInstance->outputPermutation)); @@ -787,7 +823,7 @@ TEST_F(RealParserTest, .usingNVariables(4) .usingVariables({"v1", "v2", "v3", "v4"}) .usingInputs({"i1", "i2", "i3", "i4"}) - .usingOutputs({"\"i4\"", "i3", "\"i2\"", "i1"}) + .usingOutputs({"i4", "i3", "i2", "i1"}) .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), @@ -807,12 +843,18 @@ TEST_F(RealParserTest, testing::ElementsAre(false, false, false, false)); Permutation expectedOutputPermutation; - expectedOutputPermutation.emplace(static_cast(3), - static_cast(0)); + expectedOutputPermutation.emplace(static_cast(0), + static_cast(3)); expectedOutputPermutation.emplace(static_cast(1), static_cast(2)); + expectedOutputPermutation.emplace(static_cast(2), + static_cast(1)); + + expectedOutputPermutation.emplace(static_cast(3), + static_cast(0)); + ASSERT_EQ( std::hash{}(expectedOutputPermutation), std::hash{}(quantumComputationInstance->outputPermutation)); @@ -823,9 +865,9 @@ TEST_F(RealParserTest, OutputPermutationForGarbageQubitsNotCreated) { .usingNVariables(4) .usingVariables({"v1", "v2", "v3", "v4"}) .usingInputs({"i1", "i2", "i3", "i4"}) + .withGarbageValues({isNotGarbageState, isGarbageState, isGarbageState, + isNotGarbageState}) .usingOutputs({"i4", "o1", "o2", "i1"}) - .withGarbageValues({isGarbageState, isNotGarbageState, isNotGarbageState, - isGarbageState}) .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), stringifyGate(GateType::Toffoli, {"v2"}, {"v1"}), @@ -840,16 +882,16 @@ TEST_F(RealParserTest, OutputPermutationForGarbageQubitsNotCreated) { ASSERT_EQ(0, quantumComputationInstance->getNancillae()); ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); ASSERT_THAT(quantumComputationInstance->garbage, - testing::ElementsAre(true, false, false, true)); + testing::ElementsAre(false, true, true, false)); ASSERT_THAT(quantumComputationInstance->ancillary, testing::ElementsAre(false, false, false, false)); Permutation expectedOutputPermutation; - expectedOutputPermutation.emplace(static_cast(1), - static_cast(1)); + expectedOutputPermutation.emplace(static_cast(0), + static_cast(3)); - expectedOutputPermutation.emplace(static_cast(2), - static_cast(2)); + expectedOutputPermutation.emplace(static_cast(3), + static_cast(0)); ASSERT_EQ( std::hash{}(expectedOutputPermutation), From 9ca22727928583bc0292d61fe02a3942bda9b1be Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 27 Jul 2024 16:44:57 +0000 Subject: [PATCH 19/34] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unittests/test_real_parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index 49990f18a..766ed19fa 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -781,8 +781,8 @@ TEST_F(RealParserTest, MatchingInputAndOutputInQuotes) { .usingInputs({"i1", "\"i2\"", "\"i3\"", "i4"}) .withConstants({constantValueNone, constantValueOne, constantValueZero, constantValueNone}) - .withGarbageValues({isNotGarbageState, isNotGarbageState, isNotGarbageState, - isGarbageState}) + .withGarbageValues({isNotGarbageState, isNotGarbageState, + isNotGarbageState, isGarbageState}) .usingOutputs({"i4", "\"i3\"", "\"i2\"", "o1"}) .withGates({ stringifyGate(GateType::Toffoli, {"v1"}, {"v2"}), From 8ffb011d7de1475c4af059b998fad9468b64b147 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Sat, 10 Aug 2024 19:47:41 +0200 Subject: [PATCH 20/34] Added reverse mapping from user defined garbage state of .real file to input qubits --- src/parsers/RealParser.cpp | 34 +++++++++++++++++++++++++++-- test/unittests/test_real_parser.cpp | 7 +++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index c6c253ba4..c7b91d884 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -166,6 +166,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { std::unordered_map userDefinedInputIdents; std::unordered_map userDefinedOutputIdents; std::unordered_set userDeclaredVariableIdents; + std::unordered_set outputQubitsMarkedAsGarbage; while (true) { if (!static_cast(is >> cmd)) { @@ -190,6 +191,23 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } if (cmd == ".BEGIN") { + /* + * The garbage declarations in the .real file are defined on the outputs + * while the garbage state of the quantum computation operates on the + * defined inputs, thus we perform a mapping from the output marked as + * garbage back to the input using the output permutation. + */ + for (const auto& outputQubitMarkedAsGarbage : + outputQubitsMarkedAsGarbage) { + /* + * Since the call setLogicalQubitAsGarbage(...) assumes that the qubit + * parameter is an input qubit, we need to manually mark the output qubit + * as garbage by using the output qubit instead. + */ + garbage[outputQubitMarkedAsGarbage] = true; + outputPermutation.erase(outputQubitMarkedAsGarbage); + } + // header read complete return line; } @@ -306,7 +324,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { for (const auto garbageStateValue : garbageStatePerIoDefinition) { if (const bool isCurrentQubitMarkedAsGarbage = garbageStateValue == '1'; isCurrentQubitMarkedAsGarbage) { - setLogicalQubitGarbage(static_cast(garbageStateIdx)); + outputQubitsMarkedAsGarbage.emplace(static_cast(garbageStateIdx)); } else if (garbageStateValue != '-') { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Invalid value in '.garbage' header: '" + @@ -377,7 +395,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { * The current implementation requires that the .garbage definition is * define prior to the .output one. */ - if (!logicalQubitIsGarbage(outputIoQubit)) { + if (outputQubitsMarkedAsGarbage.count(outputIoQubit) == 0) { throw QFRException("[real parser] l: " + std::to_string(line) + " msg: outputs without matching inputs are " "expected to be marked as garbage"); @@ -398,6 +416,18 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { */ outputPermutation.insert_or_assign( outputIoQubit, matchingInputQubitForOutputLiteral); + + /* + * If we have determined a non-identity permutation of an input qubit, + * (i.e. output 2 <- input 1) delete any existing identify permutation of + * the input qubit since for the output 1 of the previous identity mapping + * either another non-identity permutation must exist or the output 1 must be + * declared as garbage. + */ + if (outputPermutation.count(matchingInputQubitForOutputLiteral) > 0 && + outputPermutation[matchingInputQubitForOutputLiteral] == + matchingInputQubitForOutputLiteral) + outputPermutation.erase(matchingInputQubitForOutputLiteral); } } } else if (cmd == ".VERSION" || cmd == ".INPUTBUS" || cmd == ".OUTPUTBUS") { diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index 766ed19fa..eac1c5b9d 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -721,13 +721,14 @@ TEST_F(RealParserTest, ASSERT_EQ(0, quantumComputationInstance->getNancillae()); ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); ASSERT_THAT(quantumComputationInstance->garbage, - testing::ElementsAre(false, true, false, true)); + testing::ElementsAre(false, false, true, true)); ASSERT_THAT(quantumComputationInstance->ancillary, testing::ElementsAre(false, false, false, false)); auto expectedOutputPermutation = getIdentityPermutation(4); expectedOutputPermutation.erase(1); expectedOutputPermutation.erase(3); + expectedOutputPermutation[2] = static_cast(1); ASSERT_EQ( std::hash{}(expectedOutputPermutation), @@ -758,7 +759,7 @@ TEST_F(RealParserTest, MatchingInputAndOutputNotInQuotes) { ASSERT_EQ(2, quantumComputationInstance->getNancillae()); ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); ASSERT_THAT(quantumComputationInstance->garbage, - testing::ElementsAre(true, false, false, true)); + testing::ElementsAre(false, true, true, false)); ASSERT_THAT(quantumComputationInstance->ancillary, testing::ElementsAre(true, false, false, true)); @@ -798,7 +799,7 @@ TEST_F(RealParserTest, MatchingInputAndOutputInQuotes) { ASSERT_EQ(2, quantumComputationInstance->getNancillae()); ASSERT_EQ(1, quantumComputationInstance->getNgarbageQubits()); ASSERT_THAT(quantumComputationInstance->garbage, - testing::ElementsAre(false, false, false, true)); + testing::ElementsAre(true, false, false, false)); ASSERT_THAT(quantumComputationInstance->ancillary, testing::ElementsAre(false, true, true, false)); From 1dfffd4b7d125b418f3563a757a4bc7e05e90532 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Sat, 10 Aug 2024 23:23:32 +0200 Subject: [PATCH 21/34] Updated parsing of variable idents to use helper function which can also be reused for processing of gate list lines --- src/parsers/RealParser.cpp | 143 ++++++++++++++++++++++++++----------- 1 file changed, 103 insertions(+), 40 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index c7b91d884..49654c406 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -51,6 +51,87 @@ bool isValidIoName(const std::string_view& ioName) { }); } +std::vector +parseVariableNames(const std::size_t processedLineNumberInRealFile, + const std::size_t expectedNumberOfVariables, + const std::string& readInRawVariableIdentValues, + const std::unordered_set& variableIdentsLookup, + bool checkForDuplicates) { + std::vector variableNames; + variableNames.reserve(expectedNumberOfVariables); + + std::unordered_set processedVariableIdents; + std::size_t variableIdentStartIdx = 0; + std::size_t variableIdentEndIdx = 0; + + while (variableIdentStartIdx < readInRawVariableIdentValues.size() && + variableNames.size() <= expectedNumberOfVariables && + variableNames.size() < expectedNumberOfVariables) { + variableIdentEndIdx = + readInRawVariableIdentValues.find_first_of(' ', variableIdentStartIdx); + + if (variableIdentEndIdx == std::string::npos) + variableIdentEndIdx = readInRawVariableIdentValues.size(); + + std::size_t variableIdentLength = + variableIdentEndIdx - variableIdentStartIdx; + // On windows the line ending could be the character sequence \r\n while on + // linux system it would only be \n + if (variableIdentLength > 0 && + readInRawVariableIdentValues.at(std::min( + variableIdentEndIdx, readInRawVariableIdentValues.size() - 1)) == + '\r') + --variableIdentLength; + + const auto& variableIdent = readInRawVariableIdentValues.substr( + variableIdentStartIdx, variableIdentLength); + + if (!isValidIoName(variableIdent)) { + throw qc::QFRException( + "[real parser] l: " + std::to_string(processedLineNumberInRealFile) + + " msg: invalid variable name: " + variableIdent); + } + + if (!checkForDuplicates) { + if (variableIdentsLookup.count(variableIdent) == 0) { + throw qc::QFRException("[real parser] l: " + + std::to_string(processedLineNumberInRealFile) + + " msg: given variable name " + variableIdent + + " was not declared in .variables entry"); + } + } else if (processedVariableIdents.count(variableIdent) > 0) { + throw qc::QFRException( + "[real parser] l: " + std::to_string(processedLineNumberInRealFile) + + " msg: duplicate variable name: " + variableIdent); + } + processedVariableIdents.emplace(variableIdent); + variableNames.emplace_back(variableIdent); + variableIdentStartIdx = variableIdentEndIdx + 1; + } + + if (variableIdentEndIdx < readInRawVariableIdentValues.size() && + readInRawVariableIdentValues.at(variableIdentEndIdx) == ' ') { + throw qc::QFRException( + "[real parser] l: " + std::to_string(processedLineNumberInRealFile) + + " msg: expected only " + std::to_string(expectedNumberOfVariables) + + " variable identifiers to be declared but variable identifier " + "delimiter was found" + " after " + + std::to_string(expectedNumberOfVariables) + + " identifiers were detected (which we assume will be followed by " + "another io identifier)!"); + } + + if (variableNames.size() < expectedNumberOfVariables) { + throw qc::QFRException( + "[real parser] l:" + std::to_string(processedLineNumberInRealFile) + + " msg: Expected " + std::to_string(expectedNumberOfVariables) + + " variable idents but only " + std::to_string(variableNames.size()) + + " were declared!"); + } + return variableNames; +} + std::unordered_map parseIoNames(const std::size_t lineInRealFileDefiningIoNames, const std::size_t expectedNumberOfIos, @@ -117,19 +198,12 @@ parseIoNames(const std::size_t lineInRealFileDefiningIoNames, } ioNameStartIdx = ioNameEndIdx + 1; - /* - * We offer the user the use of some special literals to denote either - * constant inputs or garbage outputs instead of finding unique names for - * said ios, otherwise check that the given io name is unique. - */ - if (!(ioName == "0" || ioName == "1" || ioName == "g")) { - if (const auto& ioNameInsertionIntoLookupResult = - foundIoNames.emplace(ioName, static_cast(ioIdx++)); - !ioNameInsertionIntoLookupResult.second) { - throw qc::QFRException("[real parser] l: " + - std::to_string(lineInRealFileDefiningIoNames) + - " msg: duplicate io name: " + ioName); - } + if (const auto& ioNameInsertionIntoLookupResult = + foundIoNames.emplace(ioName, static_cast(ioIdx++)); + !ioNameInsertionIntoLookupResult.second) { + throw qc::QFRException( + "[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + + " msg: duplicate io name: " + ioName); } } @@ -221,41 +295,30 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } nclassics = nqubits; } else if (cmd == ".VARIABLES") { + is >> std::ws; userDeclaredVariableIdents.reserve(nclassics); - for (std::size_t i = 0; i < nclassics; ++i) { - if (!static_cast(is >> variable) || variable.at(0) == '.') { - throw QFRException( - "[real parser] l:" + std::to_string(line) + - " msg: Invalid or insufficient variables declared"); - } - if (!isValidIoName(variable)) { - throw QFRException("[real parser] l: " + std::to_string(line) + - " msg: invalid variable name: " + variable); - } + std::string variableDefinitionEntry; + if (!std::getline(is, variableDefinitionEntry)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in '.variables' line"); + } - if (userDeclaredVariableIdents.count(variable) > 0) { - throw QFRException("[real parser] l: " + std::to_string(line) + - " msg: duplicate variable name: " + variable); - } - userDeclaredVariableIdents.emplace(variable); + const auto& processedVariableIdents = + parseVariableNames(static_cast(line), nclassics, + variableDefinitionEntry, {}, true); + userDeclaredVariableIdents.insert(processedVariableIdents.cbegin(), + processedVariableIdents.cend()); + ancillary.resize(nqubits); + garbage.resize(nqubits); + for (std::size_t i = 0; i < nclassics; ++i) { const auto qubit = static_cast(i); - qregs.insert({variable, {qubit, 1U}}); - cregs.insert({"c_" + variable, {qubit, 1U}}); + qregs.insert({processedVariableIdents.at(i), {qubit, 1U}}); + cregs.insert({"c_" + processedVariableIdents.at(i), {qubit, 1U}}); initialLayout.insert({qubit, qubit}); outputPermutation.insert({qubit, qubit}); - ancillary.resize(nqubits); - garbage.resize(nqubits); } - - /* - * - * TODO: Ancillary qubits are expected to be initialized with the value - * '0' but .real file .constants definition allows the two values '0' and - * '1' - do we have to manually insert appropriate gates to set the - * value qubits marked as constants to '1' ? - */ } else if (cmd == ".CONSTANTS") { is >> std::ws; std::string constantsValuePerIoDefinition; From 6b1bcea10f190508990d1e93d355b56a2702dd32 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Sun, 11 Aug 2024 01:04:50 +0200 Subject: [PATCH 22/34] Use new helper function to validate variable idents used in gate list definitions --- src/parsers/RealParser.cpp | 93 +++++++++++++++++------------ test/unittests/test_real_parser.cpp | 1 - 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 49654c406..87ed502c4 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -56,7 +55,8 @@ parseVariableNames(const std::size_t processedLineNumberInRealFile, const std::size_t expectedNumberOfVariables, const std::string& readInRawVariableIdentValues, const std::unordered_set& variableIdentsLookup, - bool checkForDuplicates) { + bool checkForDuplicates, + const std::string_view& trimableVariableIdentPrefix) { std::vector variableNames; variableNames.reserve(expectedNumberOfVariables); @@ -83,8 +83,13 @@ parseVariableNames(const std::size_t processedLineNumberInRealFile, '\r') --variableIdentLength; - const auto& variableIdent = readInRawVariableIdentValues.substr( + auto variableIdent = readInRawVariableIdentValues.substr( variableIdentStartIdx, variableIdentLength); + const bool trimVariableIdent = + variableIdent.find_first_of(trimableVariableIdentPrefix) == 0; + if (trimVariableIdent) + variableIdent = + variableIdent.replace(0, trimableVariableIdentPrefix.size(), ""); if (!isValidIoName(variableIdent)) { throw qc::QFRException( @@ -105,7 +110,10 @@ parseVariableNames(const std::size_t processedLineNumberInRealFile, " msg: duplicate variable name: " + variableIdent); } processedVariableIdents.emplace(variableIdent); - variableNames.emplace_back(variableIdent); + variableNames.emplace_back(trimVariableIdent + ? std::string(trimableVariableIdentPrefix) + + variableIdent + : variableIdent); variableIdentStartIdx = variableIdentEndIdx + 1; } @@ -229,7 +237,6 @@ void qc::QuantumComputation::importReal(std::istream& is) { int qc::QuantumComputation::readRealHeader(std::istream& is) { std::string cmd; - std::string variable; int line = 0; /* @@ -275,8 +282,8 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { outputQubitsMarkedAsGarbage) { /* * Since the call setLogicalQubitAsGarbage(...) assumes that the qubit - * parameter is an input qubit, we need to manually mark the output qubit - * as garbage by using the output qubit instead. + * parameter is an input qubit, we need to manually mark the output + * qubit as garbage by using the output qubit instead. */ garbage[outputQubitMarkedAsGarbage] = true; outputPermutation.erase(outputQubitMarkedAsGarbage); @@ -306,7 +313,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { const auto& processedVariableIdents = parseVariableNames(static_cast(line), nclassics, - variableDefinitionEntry, {}, true); + variableDefinitionEntry, {}, true, ""); userDeclaredVariableIdents.insert(processedVariableIdents.cbegin(), processedVariableIdents.cend()); @@ -387,7 +394,8 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { for (const auto garbageStateValue : garbageStatePerIoDefinition) { if (const bool isCurrentQubitMarkedAsGarbage = garbageStateValue == '1'; isCurrentQubitMarkedAsGarbage) { - outputQubitsMarkedAsGarbage.emplace(static_cast(garbageStateIdx)); + outputQubitsMarkedAsGarbage.emplace( + static_cast(garbageStateIdx)); } else if (garbageStateValue != '-') { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Invalid value in '.garbage' header: '" + @@ -482,10 +490,10 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { /* * If we have determined a non-identity permutation of an input qubit, - * (i.e. output 2 <- input 1) delete any existing identify permutation of - * the input qubit since for the output 1 of the previous identity mapping - * either another non-identity permutation must exist or the output 1 must be - * declared as garbage. + * (i.e. output 2 <- input 1) delete any existing identify permutation + * of the input qubit since for the output 1 of the previous identity + * mapping either another non-identity permutation must exist or the + * output 1 must be declared as garbage. */ if (outputPermutation.count(matchingInputQubitForOutputLiteral) > 0 && outputPermutation[matchingInputQubitForOutputLiteral] == @@ -586,14 +594,11 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, } std::string qubits; - std::string label; - getline(is, qubits); - - std::vector controls{}; - std::istringstream iss(qubits); + if (!getline(is, qubits)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in gate definition"); + } - // TODO: Check how non-default RevLib .real specification gate types shall - // be supported i.e. c a b (which does not define the number of gate lines) const std::string& stringifiedNumberOfGateLines = m.str(2); const auto numberOfGateLines = stringifiedNumberOfGateLines.empty() @@ -613,49 +618,61 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, ncontrols = numberOfGateLines - 2; } + std::vector controls(ncontrols, Qubit()); + const auto& gateLines = qubits.empty() ? "" : qubits.substr(1); + std::unordered_set validVariableIdentLookup; + + /* Use the entries of the creg register map prefixed with 'c_' to determine + * the declared variable idents in the .variable entry + */ + for (const auto& qregNameAndQubitIndexPair : cregs) + validVariableIdentLookup.emplace( + qregNameAndQubitIndexPair.first.substr(2)); + + // We will ignore the prefix '-' when validating a given gate line ident + auto processedGateLines = + parseVariableNames(static_cast(line), numberOfGateLines, + gateLines, validVariableIdentLookup, false, "-"); + + std::size_t lineIdx = 0; // get controls and target for (std::size_t i = 0; i < ncontrols; ++i) { - if (!(iss >> label)) { - throw QFRException("[real parser] l:" + std::to_string(line) + - " msg: Too few variables for gate " + m.str(1)); - } - - const bool negativeControl = (label.at(0) == '-'); - if (negativeControl) { - label.erase(label.begin()); - } + std::string_view gateIdent = processedGateLines.at(lineIdx++); + const bool negativeControl = gateIdent.front() == '-'; + if (negativeControl) + gateIdent = gateIdent.substr(1); // Since variable qubits can either be data or ancillary qubits our search // will have to be conducted in both lookups if (const std::optional controlLineQubit = - getQubitForVariableIdentFromAnyLookup(label, qregs, ancregs); + getQubitForVariableIdentFromAnyLookup(std::string(gateIdent), + qregs, ancregs); controlLineQubit.has_value()) { controls.emplace_back(*controlLineQubit, negativeControl ? Control::Type::Neg : Control::Type::Pos); } else { throw QFRException("[real parser] l:" + std::to_string(line) + - " msg: Label " + label + " not found!"); + " msg: Matching qubit for control line " + + std::string(gateIdent) + " not found!"); } } const auto numberOfTargetLines = numberOfGateLines - ncontrols; std::vector targetLineQubits(numberOfTargetLines, Qubit()); for (std::size_t i = 0; i < numberOfTargetLines; ++i) { - if (!(iss >> label)) { - throw QFRException("[real parser] l:" + std::to_string(line) + - " msg: Too few variables (no target) for gate " + - m.str(1)); - } + const auto& targetLineIdent = processedGateLines.at(lineIdx++); // Since variable qubits can either be data or ancillary qubits our search // will have to be conducted in both lookups if (const std::optional targetLineQubit = - getQubitForVariableIdentFromAnyLookup(label, qregs, ancregs); + getQubitForVariableIdentFromAnyLookup(targetLineIdent, qregs, + ancregs); targetLineQubit.has_value()) { targetLineQubits[i] = *targetLineQubit; } else { throw QFRException("[real parser] l:" + std::to_string(line) + - " msg: Label " + label + " not found!"); + " msg: Matching qubit for target line " + + targetLineIdent + " not found!"); } } diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index eac1c5b9d..cfe9b4733 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -14,7 +14,6 @@ #include #include #include -#include using namespace qc; using ::testing::NotNull; From 81be5df2e8fe8539fa438923b41d35c7c91ee1af Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Sun, 11 Aug 2024 22:39:27 +0200 Subject: [PATCH 23/34] Add support for .intial_layout definition in .real files using variable idents instead of numeric qubit values (the latter are used in the corresponding functionality of the qasm_parser) --- src/parsers/RealParser.cpp | 48 ++++++++--- test/unittests/test_real_parser.cpp | 119 ++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 11 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 87ed502c4..73eb5d4e0 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -55,7 +55,7 @@ parseVariableNames(const std::size_t processedLineNumberInRealFile, const std::size_t expectedNumberOfVariables, const std::string& readInRawVariableIdentValues, const std::unordered_set& variableIdentsLookup, - bool checkForDuplicates, + bool allowedDuplicateVariableIdentDeclarations, const std::string_view& trimableVariableIdentPrefix) { std::vector variableNames; variableNames.reserve(expectedNumberOfVariables); @@ -97,18 +97,20 @@ parseVariableNames(const std::size_t processedLineNumberInRealFile, " msg: invalid variable name: " + variableIdent); } - if (!checkForDuplicates) { - if (variableIdentsLookup.count(variableIdent) == 0) { - throw qc::QFRException("[real parser] l: " + - std::to_string(processedLineNumberInRealFile) + - " msg: given variable name " + variableIdent + - " was not declared in .variables entry"); - } - } else if (processedVariableIdents.count(variableIdent) > 0) { + if (!allowedDuplicateVariableIdentDeclarations && + processedVariableIdents.count(variableIdent) > 0) { throw qc::QFRException( "[real parser] l: " + std::to_string(processedLineNumberInRealFile) + " msg: duplicate variable name: " + variableIdent); } + + if (!variableIdentsLookup.empty() && + variableIdentsLookup.count(variableIdent) == 0) { + throw qc::QFRException( + "[real parser] l: " + std::to_string(processedLineNumberInRealFile) + + " msg: given variable name " + variableIdent + + " was not declared in .variables entry"); + } processedVariableIdents.emplace(variableIdent); variableNames.emplace_back(trimVariableIdent ? std::string(trimableVariableIdentPrefix) + @@ -313,7 +315,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { const auto& processedVariableIdents = parseVariableNames(static_cast(line), nclassics, - variableDefinitionEntry, {}, true, ""); + variableDefinitionEntry, {}, false, ""); userDeclaredVariableIdents.insert(processedVariableIdents.cbegin(), processedVariableIdents.cend()); @@ -326,6 +328,29 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { initialLayout.insert({qubit, qubit}); outputPermutation.insert({qubit, qubit}); } + } else if (cmd == ".INITIAL_LAYOUT") { + is >> std::ws; + std::string initialLayoutDefinitionEntry; + if (!std::getline(is, initialLayoutDefinitionEntry)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in '.initial_layout' line"); + } + + const auto& processedVariableIdents = parseVariableNames( + static_cast(line), nclassics, + initialLayoutDefinitionEntry, userDeclaredVariableIdents, false, ""); + + /* Map the user declared variable idents in the .variable entry to the + * ones declared in the .initial_layout as explained in + * https://mqt.readthedocs.io/projects/core/en/latest/quickstart.html#layout-information + */ + for (std::size_t i = 0; i < nclassics; ++i) { + const auto algorithmicQubit = static_cast(i); + const auto deviceQubitForVariableIdentInInitialLayout = + qregs[processedVariableIdents.at(i)].first; + initialLayout[deviceQubitForVariableIdentInInitialLayout] = + algorithmicQubit; + } } else if (cmd == ".CONSTANTS") { is >> std::ws; std::string constantsValuePerIoDefinition; @@ -629,10 +654,11 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, validVariableIdentLookup.emplace( qregNameAndQubitIndexPair.first.substr(2)); + // TODO: Check that no control line is used as a target line // We will ignore the prefix '-' when validating a given gate line ident auto processedGateLines = parseVariableNames(static_cast(line), numberOfGateLines, - gateLines, validVariableIdentLookup, false, "-"); + gateLines, validVariableIdentLookup, true, "-"); std::size_t lineIdx = 0; // get controls and target diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index cfe9b4733..1dbd450e7 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -42,6 +42,17 @@ class RealParserTest : public testing::Test { return *this; } + RealParserTest& usingInitialLayout( + const std::initializer_list& variableIdents + ) { + realFileContent << realHeaderInitialLayoutCommandPrefix; + for (const auto& variableIdent : variableIdents) + realFileContent << " " << variableIdent; + + realFileContent << "\n"; + return *this; + } + RealParserTest& usingInputs(const std::initializer_list& inputIdents) { realFileContent << realHeaderInputCommandPrefix; @@ -105,6 +116,7 @@ class RealParserTest : public testing::Test { const std::string realHeaderVersionCommandPrefix = ".version"; const std::string realHeaderNumVarsCommandPrefix = ".numvars"; const std::string realHeaderVariablesCommandPrefix = ".variables"; + const std::string realHeaderInitialLayoutCommandPrefix = ".initial_layout"; const std::string realHeaderInputCommandPrefix = ".inputs"; const std::string realHeaderOutputCommandPrefix = ".outputs"; const std::string realHeaderConstantsCommandPrefix = ".constants"; @@ -241,6 +253,18 @@ TEST_F(RealParserTest, MoreGarbageEntriesThanVariablesDeclared) { QFRException); } +TEST_F(RealParserTest, MoreIdentsInInitialLayoutThanVariablesDeclared) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInitialLayout({"v1", "v2", "v3"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + TEST_F(RealParserTest, LessVariablesThanNumVariablesDeclared) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) @@ -300,6 +324,18 @@ TEST_F(RealParserTest, LessGarbageEntriesThanNumVariablesDeclared) { QFRException); } +TEST_F(RealParserTest, LessIdentsInInitialLayoutThanVariablesDeclared) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInitialLayout({"v2"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + TEST_F(RealParserTest, InvalidVariableIdentDeclaration) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) @@ -335,6 +371,18 @@ TEST_F(RealParserTest, InvalidInputIdentDeclarationInQuote) { QFRException); } +TEST_F(RealParserTest, InvalidVariableIdentDeclarationInInitialLayout) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInitialLayout({"v-1", "v2"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + TEST_F(RealParserTest, EmptyInputIdentInQuotesNotAllowed) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) @@ -442,6 +490,18 @@ TEST_F(RealParserTest, DuplicateOutputIdentDeclaration) { QFRException); } +TEST_F(RealParserTest, DuplicateVariableIdentDeclarationInInitialLayout) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInitialLayout({"v1", "v1"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + TEST_F(RealParserTest, MissingClosingQuoteInIoIdentifierDoesNotLeadToInfinityLoop) { usingVersion(DEFAULT_REAL_VERSION) @@ -536,6 +596,18 @@ TEST_F(RealParserTest, GateWithTargetLineTargetingUnknownVariable) { QFRException); } +TEST_F(RealParserTest, UnknownVariableIdentDeclarationInInitialLayout) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInitialLayout({"v4", "v1"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + // OK TESTS TEST_F(RealParserTest, ConstantValueZero) { usingVersion(DEFAULT_REAL_VERSION) @@ -897,3 +969,50 @@ TEST_F(RealParserTest, OutputPermutationForGarbageQubitsNotCreated) { std::hash{}(expectedOutputPermutation), std::hash{}(quantumComputationInstance->outputPermutation)); } + +TEST_F(RealParserTest, CheckIdentityInitialLayout) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInitialLayout({"v1", "v2"}) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + + Permutation expectedInitialLayout = getIdentityPermutation(2); + ASSERT_EQ( + std::hash{}(expectedInitialLayout), + std::hash{}(quantumComputationInstance->initialLayout)); +} + +TEST_F(RealParserTest, CheckNoneIdentityInitialLayout) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(4) + .usingVariables({"v1", "v2", "v3", "v4"}) + .usingInitialLayout({"v4", "v2", "v1", "v3"}) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(4, quantumComputationInstance->getNqubits()); + Permutation expectedInitialLayout; + + expectedInitialLayout.emplace(static_cast(0), + static_cast(2)); + + expectedInitialLayout.emplace(static_cast(1), + static_cast(1)); + + expectedInitialLayout.emplace(static_cast(2), + static_cast(3)); + + expectedInitialLayout.emplace(static_cast(3), + static_cast(0)); + ASSERT_EQ( + std::hash{}(expectedInitialLayout), + std::hash{}(quantumComputationInstance->initialLayout)); +} From 4cb7fb7107604a7cca40ce76ba5067c57bb8fcba Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Sun, 11 Aug 2024 22:56:43 +0200 Subject: [PATCH 24/34] clang-tidy fix for 'performance-enum-size' --- test/unittests/test_real_parser.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index 1dbd450e7..b0df2dc9a 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -43,8 +43,7 @@ class RealParserTest : public testing::Test { } RealParserTest& usingInitialLayout( - const std::initializer_list& variableIdents - ) { + const std::initializer_list& variableIdents) { realFileContent << realHeaderInitialLayoutCommandPrefix; for (const auto& variableIdent : variableIdents) realFileContent << " " << variableIdent; @@ -133,7 +132,7 @@ class RealParserTest : public testing::Test { const char isGarbageState = '1'; const char isNotGarbageState = '-'; - enum class GateType { Toffoli }; + enum class GateType : std::uint8_t { Toffoli }; std::unique_ptr quantumComputationInstance; std::stringstream realFileContent; @@ -1001,17 +1000,13 @@ TEST_F(RealParserTest, CheckNoneIdentityInitialLayout) { ASSERT_EQ(4, quantumComputationInstance->getNqubits()); Permutation expectedInitialLayout; - expectedInitialLayout.emplace(static_cast(0), - static_cast(2)); + expectedInitialLayout.emplace(static_cast(0), static_cast(2)); - expectedInitialLayout.emplace(static_cast(1), - static_cast(1)); + expectedInitialLayout.emplace(static_cast(1), static_cast(1)); - expectedInitialLayout.emplace(static_cast(2), - static_cast(3)); + expectedInitialLayout.emplace(static_cast(2), static_cast(3)); - expectedInitialLayout.emplace(static_cast(3), - static_cast(0)); + expectedInitialLayout.emplace(static_cast(3), static_cast(0)); ASSERT_EQ( std::hash{}(expectedInitialLayout), std::hash{}(quantumComputationInstance->initialLayout)); From 1e0d907e1bbea523853dd35f24427fe80c735c1c Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Sun, 11 Aug 2024 23:46:54 +0200 Subject: [PATCH 25/34] clang tidy fixes as well as fix for correct initialization of control line vector during processing of gate list entries --- src/parsers/RealParser.cpp | 6 +++--- test/unittests/test_real_parser.cpp | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 73eb5d4e0..0408b2425 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -674,9 +674,9 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, getQubitForVariableIdentFromAnyLookup(std::string(gateIdent), qregs, ancregs); controlLineQubit.has_value()) { - controls.emplace_back(*controlLineQubit, negativeControl - ? Control::Type::Neg - : Control::Type::Pos); + controls[i] = + Control(*controlLineQubit, + negativeControl ? Control::Type::Neg : Control::Type::Pos); } else { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Matching qubit for control line " + diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index b0df2dc9a..c55bb9625 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -4,6 +4,7 @@ #include "gmock/gmock-matchers.h" #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include using namespace qc; using ::testing::NotNull; @@ -981,7 +983,7 @@ TEST_F(RealParserTest, CheckIdentityInitialLayout) { ASSERT_EQ(2, quantumComputationInstance->getNqubits()); - Permutation expectedInitialLayout = getIdentityPermutation(2); + const Permutation expectedInitialLayout = getIdentityPermutation(2); ASSERT_EQ( std::hash{}(expectedInitialLayout), std::hash{}(quantumComputationInstance->initialLayout)); From 831b314919a01546b5d9f019c822295c3fd05b5a Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Tue, 13 Aug 2024 16:03:30 +0200 Subject: [PATCH 26/34] Fixed parsing error of gates not defining expected number of gate lines --- src/parsers/RealParser.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 0408b2425..5ffeb50a1 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -624,11 +625,18 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, " msg: Failed read in gate definition"); } + /* + * If we cannot determine how many gate lines are to be expected from the + * gate definition (i.e. the gate definition 'c a b' does not define the + * number of gate lines) we assume that the number of whitespaces left of + * the gate type define the number of gate lines. + */ const std::string& stringifiedNumberOfGateLines = m.str(2); - const auto numberOfGateLines = + const std::size_t numberOfGateLines = static_cast( stringifiedNumberOfGateLines.empty() - ? 0 - : std::stoul(stringifiedNumberOfGateLines, nullptr, 0); + ? std::count(qubits.cbegin(), qubits.cend(), ' ') + : std::stoul(stringifiedNumberOfGateLines, nullptr, 0)); + // Current parser implementation defines number of expected control lines // (nControl) as nLines (of gate definition) - 1. Controlled swap gate has // at most two target lines so we define the number of control lines as @@ -640,7 +648,7 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, "two qubits but only " + std::to_string(ncontrols) + " were defined"); } - ncontrols = numberOfGateLines - 2; + ncontrols = static_cast(numberOfGateLines - 2); } std::vector controls(ncontrols, Qubit()); From b2d6afd4fc3465a2a627186bb0df3877d6a5dbfd Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Tue, 13 Aug 2024 17:55:26 +0200 Subject: [PATCH 27/34] Fixed sign conversion warning when converting from std::ptrdiff_t to std::size_t --- src/parsers/RealParser.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 5ffeb50a1..9922269c7 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -631,11 +631,15 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, * number of gate lines) we assume that the number of whitespaces left of * the gate type define the number of gate lines. */ - const std::string& stringifiedNumberOfGateLines = m.str(2); - const std::size_t numberOfGateLines = static_cast( - stringifiedNumberOfGateLines.empty() - ? std::count(qubits.cbegin(), qubits.cend(), ' ') - : std::stoul(stringifiedNumberOfGateLines, nullptr, 0)); + std::size_t numberOfGateLines = 0; + if (const std::string& stringifiedNumberOfGateLines = m.str(2); + !stringifiedNumberOfGateLines.empty()) { + numberOfGateLines = static_cast( + std::stoul(stringifiedNumberOfGateLines, nullptr, 0)); + } else { + numberOfGateLines = static_cast( + std::count(qubits.cbegin(), qubits.cend(), ' ')); + } // Current parser implementation defines number of expected control lines // (nControl) as nLines (of gate definition) - 1. Controlled swap gate has @@ -662,7 +666,6 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, validVariableIdentLookup.emplace( qregNameAndQubitIndexPair.first.substr(2)); - // TODO: Check that no control line is used as a target line // We will ignore the prefix '-' when validating a given gate line ident auto processedGateLines = parseVariableNames(static_cast(line), numberOfGateLines, From 7a981365d9e0b59845f027ca3072aa20df5914ef Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 14 Aug 2024 18:23:40 +0200 Subject: [PATCH 28/34] Added check that all required header components were declared for each possible header component. Added check that no duplicate header components were defined. --- src/parsers/RealParser.cpp | 112 ++++++++++++--- test/unittests/test_real_parser.cpp | 204 ++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+), 18 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index 9922269c7..b1be85e3f 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -52,7 +52,7 @@ bool isValidIoName(const std::string_view& ioName) { } std::vector -parseVariableNames(const std::size_t processedLineNumberInRealFile, +parseVariableNames(const int processedLineNumberInRealFile, const std::size_t expectedNumberOfVariables, const std::string& readInRawVariableIdentValues, const std::unordered_set& variableIdentsLookup, @@ -144,7 +144,7 @@ parseVariableNames(const std::size_t processedLineNumberInRealFile, } std::unordered_map -parseIoNames(const std::size_t lineInRealFileDefiningIoNames, +parseIoNames(const int lineInRealFileDefiningIoNames, const std::size_t expectedNumberOfIos, const std::string& ioNameIdentsRawValues, const std::unordered_set& variableIdentLookup) { @@ -197,7 +197,7 @@ parseIoNames(const std::size_t lineInRealFileDefiningIoNames, if (!isValidIoName(ioNameToValidate)) { throw qc::QFRException( - "[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + + "[real parser] l: "+ std::to_string(lineInRealFileDefiningIoNames) + " msg: invalid io name: " + ioName); } @@ -213,7 +213,7 @@ parseIoNames(const std::size_t lineInRealFileDefiningIoNames, foundIoNames.emplace(ioName, static_cast(ioIdx++)); !ioNameInsertionIntoLookupResult.second) { throw qc::QFRException( - "[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + + "[real parser] l:" + std::to_string(lineInRealFileDefiningIoNames) + " msg: duplicate io name: " + ioName); } } @@ -222,7 +222,7 @@ parseIoNames(const std::size_t lineInRealFileDefiningIoNames, ioNameEndIdx + 1 < ioNameIdentsRawValues.size() && ioNameIdentsRawValues.at(ioNameEndIdx + 1) == ' ') { throw qc::QFRException( - "[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + + "[real parser] l:" + std::to_string(lineInRealFileDefiningIoNames) + " msg: expected only " + std::to_string(expectedNumberOfIos) + " io identifiers to be declared but io identifier delimiter was found" " after " + @@ -233,6 +233,22 @@ parseIoNames(const std::size_t lineInRealFileDefiningIoNames, return foundIoNames; } +void assertRequiredHeaderComponentsAreDefined( + int processedLine, + std::initializer_list requiredHeaderComponentPrefixes, + const std::set>& + currentUserDeclaredHeaderComponents) { + + for (const auto& requiredHeaderComponentPrefix : + requiredHeaderComponentPrefixes) + if (currentUserDeclaredHeaderComponents.count( + requiredHeaderComponentPrefix) == 0) + throw qc::QFRException( + "[real parser] l:" + std::to_string(processedLine) + + " msg: Expected " + std::string(requiredHeaderComponentPrefix) + + " to have been already defined"); +} + void qc::QuantumComputation::importReal(std::istream& is) { auto line = readRealHeader(is); readRealGateDescriptions(is, line); @@ -252,6 +268,17 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { std::unordered_set userDeclaredVariableIdents; std::unordered_set outputQubitsMarkedAsGarbage; + const std::string NUM_VARIABLES_HEADER_COMPONENT_PREFIX = ".NUMVARS"; + const std::string VARIABLES_HEADER_COMPONENT_PREFIX = ".VARIABLES"; + const std::string OUTPUTS_HEADER_COMPONENT_PREFIX = ".OUTPUTS"; + /* + * To enabled heterogenous lookup in an associative, ordered container (i.e. use + * the type std::string_view or a string literal as the lookup key without allocating + * a new string) we need to specify the transparent comparater. Heterogenuous lookup in + * unordered associative containers is a C++20 feature. + */ + std::set> definedHeaderComponents; + while (true) { if (!static_cast(is >> cmd)) { throw QFRException("[real parser] l:" + std::to_string(line) + @@ -274,7 +301,20 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { " msg: Invalid file header"); } + if (definedHeaderComponents.count(cmd) != 0) + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Duplicate definition of header component " + + cmd); + + definedHeaderComponents.emplace(cmd); if (cmd == ".BEGIN") { + // Entries .numvars and .variables must be declared in all .real files + assertRequiredHeaderComponentsAreDefined( + line, + {NUM_VARIABLES_HEADER_COMPONENT_PREFIX, + VARIABLES_HEADER_COMPONENT_PREFIX}, + definedHeaderComponents); + /* * The garbage declarations in the .real file are defined on the outputs * while the garbage state of the quantum computation operates on the @@ -306,6 +346,10 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { nclassics = nqubits; } else if (cmd == ".VARIABLES") { is >> std::ws; + assertRequiredHeaderComponentsAreDefined( + line, + {NUM_VARIABLES_HEADER_COMPONENT_PREFIX}, + definedHeaderComponents); userDeclaredVariableIdents.reserve(nclassics); std::string variableDefinitionEntry; @@ -315,7 +359,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } const auto& processedVariableIdents = - parseVariableNames(static_cast(line), nclassics, + parseVariableNames(line, nclassics, variableDefinitionEntry, {}, false, ""); userDeclaredVariableIdents.insert(processedVariableIdents.cbegin(), processedVariableIdents.cend()); @@ -331,14 +375,19 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } } else if (cmd == ".INITIAL_LAYOUT") { is >> std::ws; + assertRequiredHeaderComponentsAreDefined( + line, + {NUM_VARIABLES_HEADER_COMPONENT_PREFIX, + VARIABLES_HEADER_COMPONENT_PREFIX}, + definedHeaderComponents); + std::string initialLayoutDefinitionEntry; if (!std::getline(is, initialLayoutDefinitionEntry)) { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Failed read in '.initial_layout' line"); } - const auto& processedVariableIdents = parseVariableNames( - static_cast(line), nclassics, + const auto& processedVariableIdents = parseVariableNames(line, nclassics, initialLayoutDefinitionEntry, userDeclaredVariableIdents, false, ""); /* Map the user declared variable idents in the .variable entry to the @@ -354,6 +403,11 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } } else if (cmd == ".CONSTANTS") { is >> std::ws; + assertRequiredHeaderComponentsAreDefined( + line, + {NUM_VARIABLES_HEADER_COMPONENT_PREFIX}, + definedHeaderComponents); + std::string constantsValuePerIoDefinition; if (!std::getline(is, constantsValuePerIoDefinition)) { throw QFRException("[real parser] l:" + std::to_string(line) + @@ -362,7 +416,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { if (constantsValuePerIoDefinition.size() != nclassics) { throw QFRException( - "[real parser] l: " + std::to_string(line) + " msg: Expected " + + "[real parser] l:" + std::to_string(line) + " msg: Expected " + std::to_string(nclassics) + " constant values but " + std::to_string(constantsValuePerIoDefinition.size()) + " were declared!"); @@ -402,6 +456,11 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } } else if (cmd == ".GARBAGE") { is >> std::ws; + assertRequiredHeaderComponentsAreDefined( + line, + {NUM_VARIABLES_HEADER_COMPONENT_PREFIX}, + definedHeaderComponents); + std::string garbageStatePerIoDefinition; if (!std::getline(is, garbageStatePerIoDefinition)) { throw QFRException("[real parser] l:" + std::to_string(line) + @@ -409,7 +468,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } if (garbageStatePerIoDefinition.size() != nclassics) { - throw QFRException("[real parser] l: " + std::to_string(line) + + throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Expected " + std::to_string(nclassics) + " garbage state values but " + std::to_string(garbageStatePerIoDefinition.size()) + @@ -432,6 +491,17 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } else if (cmd == ".INPUTS") { // .INPUT: specifies initial layout is >> std::ws; + assertRequiredHeaderComponentsAreDefined( + line, + {NUM_VARIABLES_HEADER_COMPONENT_PREFIX, + VARIABLES_HEADER_COMPONENT_PREFIX}, + definedHeaderComponents); + + if (definedHeaderComponents.count(OUTPUTS_HEADER_COMPONENT_PREFIX) > 0) + throw QFRException( + "[real parser] l:" + std::to_string(line) + + " msg: .inputs entry must be declared prior to the .outputs entry"); + const std::size_t expectedNumInputIos = nclassics; std::string ioNameIdentsLine; if (!std::getline(is, ioNameIdentsLine)) { @@ -440,17 +510,23 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } userDefinedInputIdents = - parseIoNames(static_cast(line), expectedNumInputIos, + parseIoNames(line, expectedNumInputIos, ioNameIdentsLine, userDeclaredVariableIdents); if (userDefinedInputIdents.size() != expectedNumInputIos) { throw QFRException( - "[real parser] l: " + std::to_string(line) + "msg: Expected " + + "[real parser] l:" + std::to_string(line) + "msg: Expected " + std::to_string(expectedNumInputIos) + " inputs to be declared!"); } } else if (cmd == ".OUTPUTS") { // .OUTPUTS: specifies output permutation is >> std::ws; + assertRequiredHeaderComponentsAreDefined( + line, + {NUM_VARIABLES_HEADER_COMPONENT_PREFIX, + VARIABLES_HEADER_COMPONENT_PREFIX}, + definedHeaderComponents); + const std::size_t expectedNumOutputIos = nclassics; std::string ioNameIdentsLine; if (!std::getline(is, ioNameIdentsLine)) { @@ -459,12 +535,12 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } userDefinedOutputIdents = - parseIoNames(static_cast(line), expectedNumOutputIos, + parseIoNames(line, expectedNumOutputIos, ioNameIdentsLine, userDeclaredVariableIdents); if (userDefinedOutputIdents.size() != expectedNumOutputIos) { throw QFRException( - "[real parser] l: " + std::to_string(line) + "msg: Expected " + + "[real parser] l:" + std::to_string(line) + "msg: Expected " + std::to_string(expectedNumOutputIos) + " outputs to be declared!"); } @@ -493,7 +569,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { * define prior to the .output one. */ if (outputQubitsMarkedAsGarbage.count(outputIoQubit) == 0) { - throw QFRException("[real parser] l: " + std::to_string(line) + + throw QFRException("[real parser] l:" + std::to_string(line) + " msg: outputs without matching inputs are " "expected to be marked as garbage"); } @@ -647,7 +723,7 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, // nLines - 2. if (gate == SWAP) { if (numberOfGateLines < 2) { - throw QFRException("[real parser] l: " + std::to_string(line) + + throw QFRException("[real parser] l:" + std::to_string(line) + "msg: SWAP gate is expected to operate on at least " "two qubits but only " + std::to_string(ncontrols) + " were defined"); @@ -668,8 +744,8 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, // We will ignore the prefix '-' when validating a given gate line ident auto processedGateLines = - parseVariableNames(static_cast(line), numberOfGateLines, - gateLines, validVariableIdentLookup, true, "-"); + parseVariableNames(line, numberOfGateLines, gateLines, + validVariableIdentLookup, true, "-"); std::size_t lineIdx = 0; // get controls and target diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index c55bb9625..1b08e06e6 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -609,6 +609,210 @@ TEST_F(RealParserTest, UnknownVariableIdentDeclarationInInitialLayout) { QFRException); } +TEST_F(RealParserTest, DuplicateNumVarsDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingNVariables(3) + .usingVariables({"v1", "v2"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateVariablesDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i1", "i2"}) + .usingVariables({"v1", "v2"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateInputsDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i1", "i2"}) + .withConstants({constantValueOne, constantValueNone}) + .usingOutputs({"o1", "o2"}) + .usingInputs({"i1", "i2"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateConstantsDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i1", "i2"}) + .withConstants({constantValueOne, constantValueNone}) + .usingOutputs({"o1", "o2"}) + .withConstants({constantValueOne, constantValueNone}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateOutputsDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"o1", "o2"}) + .withGarbageValues({isGarbageState, isNotGarbageState}) + .usingOutputs({"o1", "o2"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateGarbageDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"o1", "o2"}) + .withGarbageValues({isGarbageState, isNotGarbageState}) + .withGarbageValues({isGarbageState, isNotGarbageState}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateInitialLayoutDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInitialLayout({"v2", "v1"}) + .withGarbageValues({isGarbageState, isNotGarbageState}) + .usingInitialLayout({"v2", "v1"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, HeaderWithoutNumVarsDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingVariables({"v1", "v2"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, HeaderWithoutVariablesDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, ContentWithoutGateListNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, VariableDefinitionPriorToNumVarsDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingVariables({"v1", "v2"}) + .usingNVariables(2); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, InputsDefinitionPrioToVariableDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingInputs({"i1", "i2"}) + .usingVariables({"v1", "v2"}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, OutputDefinitionPriorToVariableDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingOutputs({"o1", "o2"}) + .usingVariables({"v1", "v2"}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, ConstantsDefinitionPriorToNumVarsDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .withConstants({constantValueOne, constantValueZero}) + .usingNVariables(2) + .usingVariables({"v1", "v2"}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, GarbageDefinitionPriorToNumVarsDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .withGarbageValues({isGarbageState, isGarbageState}) + .usingNVariables(2) + .usingVariables({"v1", "v2"}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, InitialLayoutPriorToVariableDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingInitialLayout({"v1", "v2"}) + .usingVariables({"v1", "v2"}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, OutputsDefinitionPriorToInputDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"i2", "i1"}) + .usingInputs({"i1", "i2"}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + // OK TESTS TEST_F(RealParserTest, ConstantValueZero) { usingVersion(DEFAULT_REAL_VERSION) From 5086eeb124bc75a16238aeabdf132b43e8613f75 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 14 Aug 2024 20:19:04 +0200 Subject: [PATCH 29/34] Added validation of control and target lines and fixed some error message formats --- src/parsers/RealParser.cpp | 73 ++++++++++++--------------- test/unittests/test_real_parser.cpp | 77 +++++++++++++++++++++++++++-- 2 files changed, 105 insertions(+), 45 deletions(-) diff --git a/src/parsers/RealParser.cpp b/src/parsers/RealParser.cpp index b1be85e3f..9eb56e977 100644 --- a/src/parsers/RealParser.cpp +++ b/src/parsers/RealParser.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -15,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -41,7 +44,7 @@ std::optional getQubitForVariableIdentFromAnyLookup( /// consists of only letters, digits and underscore characters. /// @param ioName The name to valid /// @return Whether the given io name is valid -bool isValidIoName(const std::string_view& ioName) { +bool isValidIoName(const std::string_view& ioName) noexcept { return !ioName.empty() && std::all_of( ioName.cbegin(), ioName.cend(), [](const char ioNameCharacter) { @@ -56,7 +59,6 @@ parseVariableNames(const int processedLineNumberInRealFile, const std::size_t expectedNumberOfVariables, const std::string& readInRawVariableIdentValues, const std::unordered_set& variableIdentsLookup, - bool allowedDuplicateVariableIdentDeclarations, const std::string_view& trimableVariableIdentPrefix) { std::vector variableNames; variableNames.reserve(expectedNumberOfVariables); @@ -98,8 +100,7 @@ parseVariableNames(const int processedLineNumberInRealFile, " msg: invalid variable name: " + variableIdent); } - if (!allowedDuplicateVariableIdentDeclarations && - processedVariableIdents.count(variableIdent) > 0) { + if (processedVariableIdents.count(variableIdent) > 0) { throw qc::QFRException( "[real parser] l: " + std::to_string(processedLineNumberInRealFile) + " msg: duplicate variable name: " + variableIdent); @@ -197,7 +198,7 @@ parseIoNames(const int lineInRealFileDefiningIoNames, if (!isValidIoName(ioNameToValidate)) { throw qc::QFRException( - "[real parser] l: "+ std::to_string(lineInRealFileDefiningIoNames) + + "[real parser] l: " + std::to_string(lineInRealFileDefiningIoNames) + " msg: invalid io name: " + ioName); } @@ -268,14 +269,15 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { std::unordered_set userDeclaredVariableIdents; std::unordered_set outputQubitsMarkedAsGarbage; - const std::string NUM_VARIABLES_HEADER_COMPONENT_PREFIX = ".NUMVARS"; - const std::string VARIABLES_HEADER_COMPONENT_PREFIX = ".VARIABLES"; - const std::string OUTPUTS_HEADER_COMPONENT_PREFIX = ".OUTPUTS"; + constexpr std::string_view numVariablesHeaderComponentPrefix = ".NUMVARS"; + constexpr std::string_view variablesHeaderComponentPrefix = ".VARIABLES"; + constexpr std::string_view outputsHeaderComponentPrefix = ".OUTPUTS"; /* - * To enabled heterogenous lookup in an associative, ordered container (i.e. use - * the type std::string_view or a string literal as the lookup key without allocating - * a new string) we need to specify the transparent comparater. Heterogenuous lookup in - * unordered associative containers is a C++20 feature. + * To enabled heterogenous lookup in an associative, ordered container (i.e. + * use the type std::string_view or a string literal as the lookup key without + * allocating a new string) we need to specify the transparent comparater. + * Heterogenuous lookup in unordered associative containers is a C++20 + * feature. */ std::set> definedHeaderComponents; @@ -311,8 +313,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { // Entries .numvars and .variables must be declared in all .real files assertRequiredHeaderComponentsAreDefined( line, - {NUM_VARIABLES_HEADER_COMPONENT_PREFIX, - VARIABLES_HEADER_COMPONENT_PREFIX}, + {numVariablesHeaderComponentPrefix, variablesHeaderComponentPrefix}, definedHeaderComponents); /* @@ -347,9 +348,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } else if (cmd == ".VARIABLES") { is >> std::ws; assertRequiredHeaderComponentsAreDefined( - line, - {NUM_VARIABLES_HEADER_COMPONENT_PREFIX}, - definedHeaderComponents); + line, {numVariablesHeaderComponentPrefix}, definedHeaderComponents); userDeclaredVariableIdents.reserve(nclassics); std::string variableDefinitionEntry; @@ -359,8 +358,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } const auto& processedVariableIdents = - parseVariableNames(line, nclassics, - variableDefinitionEntry, {}, false, ""); + parseVariableNames(line, nclassics, variableDefinitionEntry, {}, ""); userDeclaredVariableIdents.insert(processedVariableIdents.cbegin(), processedVariableIdents.cend()); @@ -377,8 +375,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { is >> std::ws; assertRequiredHeaderComponentsAreDefined( line, - {NUM_VARIABLES_HEADER_COMPONENT_PREFIX, - VARIABLES_HEADER_COMPONENT_PREFIX}, + {numVariablesHeaderComponentPrefix, variablesHeaderComponentPrefix}, definedHeaderComponents); std::string initialLayoutDefinitionEntry; @@ -387,8 +384,9 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { " msg: Failed read in '.initial_layout' line"); } - const auto& processedVariableIdents = parseVariableNames(line, nclassics, - initialLayoutDefinitionEntry, userDeclaredVariableIdents, false, ""); + const auto& processedVariableIdents = + parseVariableNames(line, nclassics, initialLayoutDefinitionEntry, + userDeclaredVariableIdents, ""); /* Map the user declared variable idents in the .variable entry to the * ones declared in the .initial_layout as explained in @@ -404,9 +402,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } else if (cmd == ".CONSTANTS") { is >> std::ws; assertRequiredHeaderComponentsAreDefined( - line, - {NUM_VARIABLES_HEADER_COMPONENT_PREFIX}, - definedHeaderComponents); + line, {numVariablesHeaderComponentPrefix}, definedHeaderComponents); std::string constantsValuePerIoDefinition; if (!std::getline(is, constantsValuePerIoDefinition)) { @@ -457,9 +453,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } else if (cmd == ".GARBAGE") { is >> std::ws; assertRequiredHeaderComponentsAreDefined( - line, - {NUM_VARIABLES_HEADER_COMPONENT_PREFIX}, - definedHeaderComponents); + line, {numVariablesHeaderComponentPrefix}, definedHeaderComponents); std::string garbageStatePerIoDefinition; if (!std::getline(is, garbageStatePerIoDefinition)) { @@ -493,11 +487,10 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { is >> std::ws; assertRequiredHeaderComponentsAreDefined( line, - {NUM_VARIABLES_HEADER_COMPONENT_PREFIX, - VARIABLES_HEADER_COMPONENT_PREFIX}, + {numVariablesHeaderComponentPrefix, variablesHeaderComponentPrefix}, definedHeaderComponents); - if (definedHeaderComponents.count(OUTPUTS_HEADER_COMPONENT_PREFIX) > 0) + if (definedHeaderComponents.count(outputsHeaderComponentPrefix) > 0) throw QFRException( "[real parser] l:" + std::to_string(line) + " msg: .inputs entry must be declared prior to the .outputs entry"); @@ -510,8 +503,8 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } userDefinedInputIdents = - parseIoNames(line, expectedNumInputIos, - ioNameIdentsLine, userDeclaredVariableIdents); + parseIoNames(line, expectedNumInputIos, ioNameIdentsLine, + userDeclaredVariableIdents); if (userDefinedInputIdents.size() != expectedNumInputIos) { throw QFRException( @@ -523,8 +516,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { is >> std::ws; assertRequiredHeaderComponentsAreDefined( line, - {NUM_VARIABLES_HEADER_COMPONENT_PREFIX, - VARIABLES_HEADER_COMPONENT_PREFIX}, + {numVariablesHeaderComponentPrefix, variablesHeaderComponentPrefix}, definedHeaderComponents); const std::size_t expectedNumOutputIos = nclassics; @@ -535,8 +527,8 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } userDefinedOutputIdents = - parseIoNames(line, expectedNumOutputIos, - ioNameIdentsLine, userDeclaredVariableIdents); + parseIoNames(line, expectedNumOutputIos, ioNameIdentsLine, + userDeclaredVariableIdents); if (userDefinedOutputIdents.size() != expectedNumOutputIos) { throw QFRException( @@ -743,9 +735,8 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, qregNameAndQubitIndexPair.first.substr(2)); // We will ignore the prefix '-' when validating a given gate line ident - auto processedGateLines = - parseVariableNames(line, numberOfGateLines, gateLines, - validVariableIdentLookup, true, "-"); + auto processedGateLines = parseVariableNames( + line, numberOfGateLines, gateLines, validVariableIdentLookup, "-"); std::size_t lineIdx = 0; // get controls and target diff --git a/test/unittests/test_real_parser.cpp b/test/unittests/test_real_parser.cpp index 1b08e06e6..a31d6c55c 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/unittests/test_real_parser.cpp @@ -134,7 +134,7 @@ class RealParserTest : public testing::Test { const char isGarbageState = '1'; const char isNotGarbageState = '-'; - enum class GateType : std::uint8_t { Toffoli }; + enum class GateType : std::uint8_t { Toffoli, V }; std::unique_ptr quantumComputationInstance; std::stringstream realFileContent; @@ -156,6 +156,8 @@ class RealParserTest : public testing::Test { static std::string stringifyGateType(const GateType gateType) { if (gateType == GateType::Toffoli) return "t"; + if (gateType == GateType::V) + return "v"; throw std::invalid_argument("Failed to stringify gate type"); } @@ -717,9 +719,7 @@ TEST_F(RealParserTest, HeaderWithoutNumVarsDefinitionNotPossible) { } TEST_F(RealParserTest, HeaderWithoutVariablesDefinitionNotPossible) { - usingVersion(DEFAULT_REAL_VERSION) - .usingNVariables(2) - .withEmptyGateList(); + usingVersion(DEFAULT_REAL_VERSION).usingNVariables(2).withEmptyGateList(); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -813,6 +813,62 @@ TEST_F(RealParserTest, OutputsDefinitionPriorToInputDefinitionNotPossible) { QFRException); } +TEST_F(RealParserTest, DuplicateControlLineInGateDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(3) + .usingVariables({"v1", "v2", "v3"}) + .withGates( + {stringifyGate(GateType::Toffoli, {"v1", "v2", "v1"}, {"v3"})}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateTargetLineInGateDefinitionNotPossible) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(3) + .usingVariables({"v1", "v2", "v3"}) + .withGates({stringifyGate(GateType::V, {"v1"}, {"v2", "v3", "v2"})}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, NotDefinedVariableNotUsableAsControlLine) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGates({stringifyGate(GateType::Toffoli, {"v1", "v3"}, {"v2"})}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, NotDefinedVariablNotUsableAsTargetLine) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v3"})}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, GateLineNotUsableAsControlAndTargetLine) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGates({stringifyGate(GateType::Toffoli, {"v1"}, {"v1"})}); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + // OK TESTS TEST_F(RealParserTest, ConstantValueZero) { usingVersion(DEFAULT_REAL_VERSION) @@ -1217,3 +1273,16 @@ TEST_F(RealParserTest, CheckNoneIdentityInitialLayout) { std::hash{}(expectedInitialLayout), std::hash{}(quantumComputationInstance->initialLayout)); } + +TEST_F(RealParserTest, GateWithoutExplicitNumGateLinesDefinitionOk) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGates({stringifyGate(GateType::V, {}, {"v1", "v2"})}); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(1, quantumComputationInstance->getNops()); +} From d7255c87a16ec4bc08ac385cfed459d16472ee0f Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 14 Aug 2024 21:02:47 +0200 Subject: [PATCH 30/34] Moved real parser tests to correct folder after merge of changes from main branch --- test/{unittests => ir}/test_real_parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename test/{unittests => ir}/test_real_parser.cpp (99%) diff --git a/test/unittests/test_real_parser.cpp b/test/ir/test_real_parser.cpp similarity index 99% rename from test/unittests/test_real_parser.cpp rename to test/ir/test_real_parser.cpp index a31d6c55c..5a0100294 100644 --- a/test/unittests/test_real_parser.cpp +++ b/test/ir/test_real_parser.cpp @@ -1,6 +1,6 @@ #include "Definitions.hpp" -#include "Permutation.hpp" -#include "QuantumComputation.hpp" +#include "ir/Permutation.hpp" +#include "ir/QuantumComputation.hpp" #include "gmock/gmock-matchers.h" #include From e8a7a0100eed11f1b76d92e9cb91eb3af83f97ed Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Wed, 14 Aug 2024 21:40:03 +0200 Subject: [PATCH 31/34] Moved file-scope functions into anonymous namespace --- src/ir/parsers/RealParser.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ir/parsers/RealParser.cpp b/src/ir/parsers/RealParser.cpp index ced02a0cb..406f4b8ca 100644 --- a/src/ir/parsers/RealParser.cpp +++ b/src/ir/parsers/RealParser.cpp @@ -25,6 +25,11 @@ #include #include +/* + * Use the anonymous namespace to declare the file-scope functions only + * used in the current source file instead of static functions + */ +namespace { std::optional getQubitForVariableIdentFromAnyLookup( const std::string& variableIdent, const qc::QuantumRegisterMap& dataQubits, const qc::QuantumRegisterMap& ancillaryQubits) { @@ -249,6 +254,7 @@ void assertRequiredHeaderComponentsAreDefined( " msg: Expected " + std::string(requiredHeaderComponentPrefix) + " to have been already defined"); } +} // namespace void qc::QuantumComputation::importReal(std::istream& is) { auto line = readRealHeader(is); From e5f88eafb96e091cace36a752d01c464a6753dac Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:40:41 +0000 Subject: [PATCH 32/34] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ir/parsers/RealParser.cpp | 2 +- test/ir/test_real_parser.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir/parsers/RealParser.cpp b/src/ir/parsers/RealParser.cpp index 406f4b8ca..9472ecff5 100644 --- a/src/ir/parsers/RealParser.cpp +++ b/src/ir/parsers/RealParser.cpp @@ -281,7 +281,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { /* * To enabled heterogenous lookup in an associative, ordered container (i.e. * use the type std::string_view or a string literal as the lookup key without - * allocating a new string) we need to specify the transparent comparater. + * allocating a new string) we need to specify the transparent comparator. * Heterogenuous lookup in unordered associative containers is a C++20 * feature. */ diff --git a/test/ir/test_real_parser.cpp b/test/ir/test_real_parser.cpp index 5a0100294..4e2f30087 100644 --- a/test/ir/test_real_parser.cpp +++ b/test/ir/test_real_parser.cpp @@ -847,7 +847,7 @@ TEST_F(RealParserTest, NotDefinedVariableNotUsableAsControlLine) { QFRException); } -TEST_F(RealParserTest, NotDefinedVariablNotUsableAsTargetLine) { +TEST_F(RealParserTest, NotDefinedVariableNotUsableAsTargetLine) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) From c19726c4932be3eb7645eec60d2bea5a88b1a848 Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Thu, 15 Aug 2024 01:34:55 +0200 Subject: [PATCH 33/34] Added handling of comment lines as well as additional whitespace characters for in header as well as gate definitions --- src/ir/parsers/RealParser.cpp | 27 +++ test/ir/test_real_parser.cpp | 354 +++++++++++++++++++++++++++++----- 2 files changed, 336 insertions(+), 45 deletions(-) diff --git a/src/ir/parsers/RealParser.cpp b/src/ir/parsers/RealParser.cpp index 9472ecff5..34058c7df 100644 --- a/src/ir/parsers/RealParser.cpp +++ b/src/ir/parsers/RealParser.cpp @@ -254,6 +254,26 @@ void assertRequiredHeaderComponentsAreDefined( " msg: Expected " + std::string(requiredHeaderComponentPrefix) + " to have been already defined"); } + +void trimCommentAndTrailingWhitespaceData(std::string& lineToProcess) { + if (const auto commentLinePrefixPosition = lineToProcess.find_first_of('#'); + commentLinePrefixPosition != std::string::npos) { + if (commentLinePrefixPosition != 0) + lineToProcess = lineToProcess.substr(0, commentLinePrefixPosition); + else + lineToProcess = ""; + } + + if (lineToProcess.empty()) + return; + + if (const std::size_t positionOfLastDataCharacter = + lineToProcess.find_last_not_of(" \t"); + positionOfLastDataCharacter != std::string::npos && + positionOfLastDataCharacter != lineToProcess.size() - 1) { + lineToProcess = lineToProcess.substr(0, positionOfLastDataCharacter + 1); + } +} } // namespace void qc::QuantumComputation::importReal(std::istream& is) { @@ -363,6 +383,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { " msg: Failed read in '.variables' line"); } + trimCommentAndTrailingWhitespaceData(variableDefinitionEntry); const auto& processedVariableIdents = parseVariableNames(line, nclassics, variableDefinitionEntry, {}, ""); userDeclaredVariableIdents.insert(processedVariableIdents.cbegin(), @@ -390,6 +411,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { " msg: Failed read in '.initial_layout' line"); } + trimCommentAndTrailingWhitespaceData(initialLayoutDefinitionEntry); const auto& processedVariableIdents = parseVariableNames(line, nclassics, initialLayoutDefinitionEntry, userDeclaredVariableIdents, ""); @@ -416,6 +438,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { " msg: Failed read in '.constants' line"); } + trimCommentAndTrailingWhitespaceData(constantsValuePerIoDefinition); if (constantsValuePerIoDefinition.size() != nclassics) { throw QFRException( "[real parser] l:" + std::to_string(line) + " msg: Expected " + @@ -467,6 +490,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { " msg: Failed read in '.garbage' line"); } + trimCommentAndTrailingWhitespaceData(garbageStatePerIoDefinition); if (garbageStatePerIoDefinition.size() != nclassics) { throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Expected " + std::to_string(nclassics) + @@ -508,6 +532,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { " msg: Failed read in '.inputs' line"); } + trimCommentAndTrailingWhitespaceData(ioNameIdentsLine); userDefinedInputIdents = parseIoNames(line, expectedNumInputIos, ioNameIdentsLine, userDeclaredVariableIdents); @@ -532,6 +557,7 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { " msg: Failed read in '.outputs' line"); } + trimCommentAndTrailingWhitespaceData(ioNameIdentsLine); userDefinedOutputIdents = parseIoNames(line, expectedNumOutputIos, ioNameIdentsLine, userDeclaredVariableIdents); @@ -698,6 +724,7 @@ void qc::QuantumComputation::readRealGateDescriptions(std::istream& is, throw QFRException("[real parser] l:" + std::to_string(line) + " msg: Failed read in gate definition"); } + trimCommentAndTrailingWhitespaceData(qubits); /* * If we cannot determine how many gate lines are to be expected from the diff --git a/test/ir/test_real_parser.cpp b/test/ir/test_real_parser.cpp index 4e2f30087..39bb6b2da 100644 --- a/test/ir/test_real_parser.cpp +++ b/test/ir/test_real_parser.cpp @@ -36,61 +36,88 @@ class RealParserTest : public testing::Test { RealParserTest& usingVariables( const std::initializer_list& variableIdents) { - realFileContent << realHeaderVariablesCommandPrefix; - for (const auto& variableIdent : variableIdents) - realFileContent << " " << variableIdent; + return usingVariables(variableIdents, std::nullopt); + } - realFileContent << "\n"; + RealParserTest& + usingVariables(const std::initializer_list& variableIdents, + const std::optional& optionalPostfix) { + pipeStringifiedCollectionToStream(realFileContent, + realHeaderVariablesCommandPrefix, + variableIdents, " ", optionalPostfix); return *this; } RealParserTest& usingInitialLayout( const std::initializer_list& variableIdents) { - realFileContent << realHeaderInitialLayoutCommandPrefix; - for (const auto& variableIdent : variableIdents) - realFileContent << " " << variableIdent; + return usingInitialLayout(variableIdents, std::nullopt); + } - realFileContent << "\n"; + RealParserTest& usingInitialLayout( + const std::initializer_list& variableIdents, + const std::optional& optionalPostfix) { + pipeStringifiedCollectionToStream(realFileContent, + realHeaderInitialLayoutCommandPrefix, + variableIdents, " ", optionalPostfix); return *this; } RealParserTest& usingInputs(const std::initializer_list& inputIdents) { - realFileContent << realHeaderInputCommandPrefix; - for (const auto& inputIdent : inputIdents) - realFileContent << " " << inputIdent; + return usingInputs(inputIdents, std::nullopt); + } - realFileContent << "\n"; + RealParserTest& + usingInputs(const std::initializer_list& inputIdents, + const std::optional& optionalPostfix) { + pipeStringifiedCollectionToStream(realFileContent, + realHeaderInputCommandPrefix, inputIdents, + " ", optionalPostfix); return *this; } RealParserTest& usingOutputs(const std::initializer_list& outputIdents) { - realFileContent << realHeaderOutputCommandPrefix; - for (const auto& outputIdent : outputIdents) - realFileContent << " " << outputIdent; + return usingOutputs(outputIdents, std::nullopt); + } - realFileContent << "\n"; + RealParserTest& + usingOutputs(const std::initializer_list& outputIdents, + const std::optional& optionalPostfix) { + pipeStringifiedCollectionToStream(realFileContent, + realHeaderOutputCommandPrefix, + outputIdents, " ", optionalPostfix); return *this; } RealParserTest& withConstants(const std::initializer_list& constantValuePerVariable) { - realFileContent << realHeaderConstantsCommandPrefix << " "; - for (const auto& constantValue : constantValuePerVariable) - realFileContent << constantValue; + return withConstants(constantValuePerVariable, std::nullopt); + } - realFileContent << "\n"; + RealParserTest& + withConstants(const std::initializer_list& constantValuePerVariable, + const std::optional& optionalPostfix) { + const std::string concatinatedConstantValues(constantValuePerVariable); + pipeStringifiedCollectionToStream( + realFileContent, realHeaderConstantsCommandPrefix + " ", + {concatinatedConstantValues}, "", optionalPostfix); return *this; } RealParserTest& withGarbageValues( const std::initializer_list& isGarbageValuePerVariable) { - realFileContent << realHeaderGarbageCommandPrefix << " "; - for (const auto& garbageValue : isGarbageValuePerVariable) - realFileContent << garbageValue; + return withGarbageValues(isGarbageValuePerVariable, std::nullopt); + } + + RealParserTest& withGarbageValues( + const std::initializer_list& isGarbageValuePerVariable, + const std::optional& optionalPostfix) { - realFileContent << "\n"; + const std::string concatinatedIsGarbageValues(isGarbageValuePerVariable); + pipeStringifiedCollectionToStream( + realFileContent, realHeaderGarbageCommandPrefix + " ", + {concatinatedIsGarbageValues}, "", optionalPostfix); return *this; } @@ -105,11 +132,11 @@ class RealParserTest : public testing::Test { if (stringifiedGateList.size() == 0) return withEmptyGateList(); - realFileContent << realHeaderGateListPrefix << "\n"; + realFileContent << realHeaderGateListPrefix; for (const auto& stringifiedGate : stringifiedGateList) - realFileContent << stringifiedGate << "\n"; + realFileContent << "\n" << stringifiedGate; - realFileContent << reakHeaderGateListPostfix; + realFileContent << "\n" << reakHeaderGateListPostfix; return *this; } @@ -133,12 +160,32 @@ class RealParserTest : public testing::Test { const char isGarbageState = '1'; const char isNotGarbageState = '-'; + static constexpr char COMMENT_LINE_PREFIX = '#'; enum class GateType : std::uint8_t { Toffoli, V }; std::unique_ptr quantumComputationInstance; std::stringstream realFileContent; + static void pipeStringifiedCollectionToStream( + std::stringstream& pipedToStream, std::string_view elementsPrefix, + const std::initializer_list& elements, + std::string_view elementDelimiter, + const std::optional& optionalPostfix) { + pipedToStream << elementsPrefix; + for (const auto& element : elements) + pipedToStream << elementDelimiter << element; + + if (optionalPostfix.has_value()) + pipedToStream << optionalPostfix.value(); + + pipedToStream << "\n"; + } + + static std::string createComment(std::string_view commentData) { + return std::string(1, COMMENT_LINE_PREFIX) + std::string(commentData); + } + void SetUp() override { quantumComputationInstance = std::make_unique(); ASSERT_THAT(quantumComputationInstance, NotNull()); @@ -166,14 +213,16 @@ class RealParserTest : public testing::Test { stringifyGate(const GateType gateType, const std::initializer_list& controlLines, const std::initializer_list& targetLines) { - return stringifyGate(gateType, std::nullopt, controlLines, targetLines); + return stringifyGate(gateType, std::nullopt, controlLines, targetLines, + std::nullopt); } static std::string stringifyGate(const GateType gateType, const std::optional& optionalNumberOfGateLines, const std::initializer_list& controlLines, - const std::initializer_list& targetLines) { + const std::initializer_list& targetLines, + const std::optional& optionalPostfix) { EXPECT_TRUE(targetLines.size() > static_cast(0)) << "Gate must have at least one line defined"; @@ -192,6 +241,9 @@ class RealParserTest : public testing::Test { for (const auto& targetLine : targetLines) stringifiedGateBuffer << " " << targetLine; + if (optionalPostfix.has_value()) + stringifiedGateBuffer << optionalPostfix.value(); + return stringifiedGateBuffer.str(); } }; @@ -339,7 +391,7 @@ TEST_F(RealParserTest, LessIdentsInInitialLayoutThanVariablesDeclared) { QFRException); } -TEST_F(RealParserTest, InvalidVariableIdentDeclaration) { +TEST_F(RealParserTest, InvalidVariableIdentDefinition) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"variable-1", "v2"}) @@ -350,7 +402,7 @@ TEST_F(RealParserTest, InvalidVariableIdentDeclaration) { QFRException); } -TEST_F(RealParserTest, InvalidInputIdentDeclaration) { +TEST_F(RealParserTest, InvalidInputIdentDefinition) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) @@ -362,7 +414,7 @@ TEST_F(RealParserTest, InvalidInputIdentDeclaration) { QFRException); } -TEST_F(RealParserTest, InvalidInputIdentDeclarationInQuote) { +TEST_F(RealParserTest, InvalidInputIdentDefinitionInQuote) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) @@ -374,7 +426,7 @@ TEST_F(RealParserTest, InvalidInputIdentDeclarationInQuote) { QFRException); } -TEST_F(RealParserTest, InvalidVariableIdentDeclarationInInitialLayout) { +TEST_F(RealParserTest, InvalidVariableIdentDefinitionInInitialLayout) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) @@ -398,7 +450,7 @@ TEST_F(RealParserTest, EmptyInputIdentInQuotesNotAllowed) { QFRException); } -TEST_F(RealParserTest, InvalidOutputIdentDeclaration) { +TEST_F(RealParserTest, InvalidOutputIdentDefinition) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) @@ -410,7 +462,7 @@ TEST_F(RealParserTest, InvalidOutputIdentDeclaration) { QFRException); } -TEST_F(RealParserTest, InvalidOutputIdentDeclarationInQuote) { +TEST_F(RealParserTest, InvalidOutputIdentDefinitionInQuote) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) @@ -458,7 +510,7 @@ TEST_F(RealParserTest, OutputIdentMatchingVariableIdentIsNotAllowed) { QFRException); } -TEST_F(RealParserTest, DuplicateVariableIdentDeclaration) { +TEST_F(RealParserTest, DuplicateVariableIdentDefinition) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v1"}) @@ -469,7 +521,7 @@ TEST_F(RealParserTest, DuplicateVariableIdentDeclaration) { QFRException); } -TEST_F(RealParserTest, DuplicateInputIdentDeclaration) { +TEST_F(RealParserTest, DuplicateInputIdentDefinition) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) @@ -481,7 +533,7 @@ TEST_F(RealParserTest, DuplicateInputIdentDeclaration) { QFRException); } -TEST_F(RealParserTest, DuplicateOutputIdentDeclaration) { +TEST_F(RealParserTest, DuplicateOutputIdentDefinition) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) @@ -493,7 +545,7 @@ TEST_F(RealParserTest, DuplicateOutputIdentDeclaration) { QFRException); } -TEST_F(RealParserTest, DuplicateVariableIdentDeclarationInInitialLayout) { +TEST_F(RealParserTest, DuplicateVariableIdentDefinitionInInitialLayout) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) @@ -558,7 +610,7 @@ TEST_F(RealParserTest, GateWithMoreLinesThanDeclared) { .usingNVariables(3) .usingVariables({"v1", "v2", "v3"}) .withGates({stringifyGate(GateType::Toffoli, std::optional(2), - {"v1", "v2"}, {"v3"})}); + {"v1", "v2"}, {"v3"}, std::nullopt)}); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -569,8 +621,8 @@ TEST_F(RealParserTest, GateWithLessLinesThanDeclared) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(3) .usingVariables({"v1", "v2", "v3"}) - .withGates( - {stringifyGate(GateType::Toffoli, std::optional(3), {"v1"}, {"v3"})}); + .withGates({stringifyGate(GateType::Toffoli, std::optional(3), {"v1"}, + {"v3"}, std::nullopt)}); EXPECT_THROW( quantumComputationInstance->import(realFileContent, Format::Real), @@ -599,7 +651,7 @@ TEST_F(RealParserTest, GateWithTargetLineTargetingUnknownVariable) { QFRException); } -TEST_F(RealParserTest, UnknownVariableIdentDeclarationInInitialLayout) { +TEST_F(RealParserTest, UnknownVariableIdentDefinitionInInitialLayout) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) @@ -946,7 +998,7 @@ TEST_F(RealParserTest, GarbageValues) { std::hash{}(quantumComputationInstance->outputPermutation)); } -TEST_F(RealParserTest, InputIdentDeclarationInQuotes) { +TEST_F(RealParserTest, InputIdentDefinitionInQuotes) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) @@ -970,7 +1022,7 @@ TEST_F(RealParserTest, InputIdentDeclarationInQuotes) { std::hash{}(quantumComputationInstance->outputPermutation)); } -TEST_F(RealParserTest, OutputIdentDeclarationInQuotes) { +TEST_F(RealParserTest, OutputIdentDefinitionInQuotes) { usingVersion(DEFAULT_REAL_VERSION) .usingNVariables(2) .usingVariables({"v1", "v2"}) @@ -1286,3 +1338,215 @@ TEST_F(RealParserTest, GateWithoutExplicitNumGateLinesDefinitionOk) { ASSERT_EQ(2, quantumComputationInstance->getNqubits()); ASSERT_EQ(1, quantumComputationInstance->getNops()); } + +TEST_F(RealParserTest, VariableDefinitionWithCommentLineAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}, + std::make_optional(createComment(" a test comment"))) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); +} + +TEST_F(RealParserTest, VariableDefinitionWithWhitespacePostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}, std::make_optional(" \t\t \t")) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); +} + +TEST_F(RealParserTest, InitialLayoutDefinitionWithCommentLineAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInitialLayout({"v2", "v1"}, + std::make_optional(createComment(" a test comment"))) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); +} + +TEST_F(RealParserTest, InitialLayoutDefinitionWithWhitespaceAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInitialLayout({"v2", "v1"}, std::make_optional(" \t\t \t")) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); +} + +TEST_F(RealParserTest, InputsDefinitionWithCommentLineAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i2", "i1"}, + std::make_optional(createComment(" a test comment"))) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); +} + +TEST_F(RealParserTest, InputsDefinitionWithWhitespaceAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i2", "i1"}, std::make_optional(" \t\t \t")) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); +} + +TEST_F(RealParserTest, ConstantsDefinitionWithCommentLineAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withConstants({constantValueOne, constantValueNone}, + std::make_optional(createComment(" a test comment"))) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(1, quantumComputationInstance->getNancillae()); + ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(true, false)); +} + +TEST_F(RealParserTest, ConstantsDefinitionWithWhitespaceAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withConstants({constantValueOne, constantValueNone}, + std::make_optional(" \t\t \t")) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(1, quantumComputationInstance->getNancillae()); + ASSERT_EQ(0, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(true, false)); +} + +TEST_F(RealParserTest, OutputsDefinitionWithCommentLineAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"o2", "o1"}, + std::make_optional(createComment(" a test comment"))) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); +} + +TEST_F(RealParserTest, OutputsDefinitionWithWhitespaceAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"o2", "o1"}, std::make_optional(" \t\t \t")) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); +} + +TEST_F(RealParserTest, GarbageDefinitionWithCommentLineAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGarbageValues({isGarbageState, isNotGarbageState}, + std::make_optional(createComment(" a test comment"))) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(1, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(true, false)); +} + +TEST_F(RealParserTest, GarbageDefinitionWithWhitespaceAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGarbageValues({isNotGarbageState, isGarbageState}, + std::make_optional(" \t\t \t")) + .withEmptyGateList(); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(1, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(false, true)); +} + +TEST_F(RealParserTest, GateDefinitionWithCommentLineAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGates({stringifyGate( + GateType::Toffoli, std::nullopt, {"v1"}, {"v2"}, + std::make_optional(createComment(" a test comment")))}); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(1, quantumComputationInstance->getNops()); +} + +TEST_F(RealParserTest, GateDefinitionWithWhitespaceAsPostfix) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGates({stringifyGate(GateType::Toffoli, std::nullopt, {"v1"}, {"v2"}, + std::make_optional(" \t\t \t"))}); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(1, quantumComputationInstance->getNops()); +} + +TEST_F(RealParserTest, CombinationOfCommentLineAndWhitespacePostfixAllowed) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables( + {"v1", "v2"}, + std::make_optional(" \t\t \t" + createComment(" a test comment"))) + .withGates({stringifyGate( + GateType::Toffoli, std::nullopt, {"v1"}, {"v2"}, + std::make_optional(" \t\t \t" + createComment(" a test comment")))}); + + EXPECT_NO_THROW( + quantumComputationInstance->import(realFileContent, Format::Real)); + + ASSERT_EQ(2, quantumComputationInstance->getNqubits()); + ASSERT_EQ(1, quantumComputationInstance->getNops()); +} From c4585316e3ae3c6bb21561ab706594995d4b3d0f Mon Sep 17 00:00:00 2001 From: Fabian Hingerl Date: Thu, 15 Aug 2024 02:15:16 +0200 Subject: [PATCH 34/34] Fixed some typos --- test/ir/test_real_parser.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ir/test_real_parser.cpp b/test/ir/test_real_parser.cpp index 39bb6b2da..55db19734 100644 --- a/test/ir/test_real_parser.cpp +++ b/test/ir/test_real_parser.cpp @@ -98,10 +98,10 @@ class RealParserTest : public testing::Test { RealParserTest& withConstants(const std::initializer_list& constantValuePerVariable, const std::optional& optionalPostfix) { - const std::string concatinatedConstantValues(constantValuePerVariable); + const std::string concatenatedConstantValues(constantValuePerVariable); pipeStringifiedCollectionToStream( realFileContent, realHeaderConstantsCommandPrefix + " ", - {concatinatedConstantValues}, "", optionalPostfix); + {concatenatedConstantValues}, "", optionalPostfix); return *this; } @@ -114,10 +114,10 @@ class RealParserTest : public testing::Test { const std::initializer_list& isGarbageValuePerVariable, const std::optional& optionalPostfix) { - const std::string concatinatedIsGarbageValues(isGarbageValuePerVariable); + const std::string concatenatedIsGarbageValues(isGarbageValuePerVariable); pipeStringifiedCollectionToStream( realFileContent, realHeaderGarbageCommandPrefix + " ", - {concatinatedIsGarbageValues}, "", optionalPostfix); + {concatenatedIsGarbageValues}, "", optionalPostfix); return *this; }