diff --git a/src/ir/parsers/RealParser.cpp b/src/ir/parsers/RealParser.cpp index fa3c960eb..34058c7df 100644 --- a/src/ir/parsers/RealParser.cpp +++ b/src/ir/parsers/RealParser.cpp @@ -7,16 +7,275 @@ #include #include #include +#include +#include +#include #include #include #include #include #include +#include #include -#include +#include #include +#include +#include +#include +#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) { + 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) noexcept { + return !ioName.empty() && + std::all_of( + ioName.cbegin(), ioName.cend(), [](const char ioNameCharacter) { + return static_cast(std::isalnum( + static_cast(ioNameCharacter))) || + ioNameCharacter == '_'; + }); +} + +std::vector +parseVariableNames(const int processedLineNumberInRealFile, + const std::size_t expectedNumberOfVariables, + const std::string& readInRawVariableIdentValues, + const std::unordered_set& variableIdentsLookup, + const std::string_view& trimableVariableIdentPrefix) { + 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; + + 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( + "[real parser] l: " + std::to_string(processedLineNumberInRealFile) + + " msg: invalid variable name: " + variableIdent); + } + + if (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) + + variableIdent + : 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 int lineInRealFileDefiningIoNames, + const std::size_t expectedNumberOfIos, + 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) { + searchingForWhitespaceCharacter = + ioNameIdentsRawValues.at(ioNameStartIdx) != '"'; + if (searchingForWhitespaceCharacter) + ioNameEndIdx = ioNameIdentsRawValues.find_first_of(' ', ioNameStartIdx); + else + ioNameEndIdx = + ioNameIdentsRawValues.find_first_of('"', ioNameStartIdx + 1); + + 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 += + 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 > 0 && + ioNameIdentsRawValues.at( + std::min(ioNameEndIdx, ioNameIdentsRawValues.size() - 1)) == '\r') + --ioNameLength; + + const auto& ioName = + ioNameIdentsRawValues.substr(ioNameStartIdx, ioNameLength); + + 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) + + " 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; + 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 (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; +} + +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 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) { auto line = readRealHeader(is); readRealGateDescriptions(is, line); @@ -24,9 +283,30 @@ void qc::QuantumComputation::importReal(std::istream& is) { int qc::QuantumComputation::readRealHeader(std::istream& is) { std::string cmd; - 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; + std::unordered_map userDefinedOutputIdents; + std::unordered_set userDeclaredVariableIdents; + std::unordered_set outputQubitsMarkedAsGarbage; + + 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 comparator. + * 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) + @@ -49,7 +329,36 @@ 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, + {numVariablesHeaderComponentPrefix, variablesHeaderComponentPrefix}, + definedHeaderComponents); + + /* + * 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; } @@ -63,42 +372,262 @@ int qc::QuantumComputation::readRealHeader(std::istream& is) { } nclassics = nqubits; } else if (cmd == ".VARIABLES") { - for (std::size_t i = 0; i < nqubits; ++i) { - if (!static_cast(is >> variable) || variable.at(0) == '.') { - throw QFRException( - "[real parser] l:" + std::to_string(line) + - " msg: Invalid or insufficient variables declared"); - } + is >> std::ws; + assertRequiredHeaderComponentsAreDefined( + line, {numVariablesHeaderComponentPrefix}, definedHeaderComponents); + userDeclaredVariableIdents.reserve(nclassics); + + std::string variableDefinitionEntry; + if (!std::getline(is, variableDefinitionEntry)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in '.variables' line"); + } + + trimCommentAndTrailingWhitespaceData(variableDefinitionEntry); + const auto& processedVariableIdents = + parseVariableNames(line, nclassics, variableDefinitionEntry, {}, ""); + 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); + } + } else if (cmd == ".INITIAL_LAYOUT") { + is >> std::ws; + assertRequiredHeaderComponentsAreDefined( + line, + {numVariablesHeaderComponentPrefix, variablesHeaderComponentPrefix}, + 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"); + } + + trimCommentAndTrailingWhitespaceData(initialLayoutDefinitionEntry); + 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 + * 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; - for (std::size_t i = 0; i < nqubits; ++i) { - const auto value = is.get(); - if (!is.good()) { + assertRequiredHeaderComponentsAreDefined( + line, {numVariablesHeaderComponentPrefix}, definedHeaderComponents); + + std::string constantsValuePerIoDefinition; + if (!std::getline(is, constantsValuePerIoDefinition)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in '.constants' line"); + } + + trimCommentAndTrailingWhitespaceData(constantsValuePerIoDefinition); + 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 = + 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); + + /* + * 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. + */ + const std::string& associatedVariableNameForQubitRegister = + getQubitRegister(ancillaryQubit); + qregs.erase(associatedVariableNameForQubitRegister); + ancregs.insert_or_assign( + associatedVariableNameForQubitRegister, + qc::QuantumRegister(std::make_pair(ancillaryQubit, 1U))); + } else if (constantValuePerIo != '-') { throw QFRException("[real parser] l:" + std::to_string(line) + - " msg: Failed read in '.constants' line"); + " msg: Invalid value in '.constants' header: '" + + std::to_string(constantValuePerIo) + "'"); } - if (value == '1') { - x(static_cast(i)); - } else if (value != '-' && value != '0') { + ++constantValueIdx; + } + } else if (cmd == ".GARBAGE") { + is >> std::ws; + assertRequiredHeaderComponentsAreDefined( + line, {numVariablesHeaderComponentPrefix}, definedHeaderComponents); + + std::string garbageStatePerIoDefinition; + if (!std::getline(is, garbageStatePerIoDefinition)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " 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) + + " 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) { + outputQubitsMarkedAsGarbage.emplace( + static_cast(garbageStateIdx)); + } else if (garbageStateValue != '-') { throw QFRException("[real parser] l:" + std::to_string(line) + - " msg: Invalid value in '.constants' header: '" + - std::to_string(value) + "'"); + " msg: Invalid value in '.garbage' header: '" + + std::to_string(garbageStateValue) + "'"); } + garbageStateIdx++; } - 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 == ".INPUTS") { + // .INPUT: specifies initial layout + is >> std::ws; + assertRequiredHeaderComponentsAreDefined( + line, + {numVariablesHeaderComponentPrefix, variablesHeaderComponentPrefix}, + definedHeaderComponents); + + 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"); + + const std::size_t expectedNumInputIos = nclassics; + std::string ioNameIdentsLine; + if (!std::getline(is, ioNameIdentsLine)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in '.inputs' line"); + } + + trimCommentAndTrailingWhitespaceData(ioNameIdentsLine); + userDefinedInputIdents = + parseIoNames(line, expectedNumInputIos, ioNameIdentsLine, + userDeclaredVariableIdents); + + if (userDefinedInputIdents.size() != expectedNumInputIos) { + 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; + assertRequiredHeaderComponentsAreDefined( + line, + {numVariablesHeaderComponentPrefix, variablesHeaderComponentPrefix}, + definedHeaderComponents); + + const std::size_t expectedNumOutputIos = nclassics; + std::string ioNameIdentsLine; + if (!std::getline(is, ioNameIdentsLine)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in '.outputs' line"); + } + + trimCommentAndTrailingWhitespaceData(ioNameIdentsLine); + userDefinedOutputIdents = + parseIoNames(line, expectedNumOutputIos, ioNameIdentsLine, + userDeclaredVariableIdents); + + if (userDefinedOutputIdents.size() != expectedNumOutputIos) { + throw QFRException( + "[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, + * with the input literal being defined at position i in the .input + * 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 + * (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) == 0) { + /* + * The current implementation requires that the .garbage definition is + * define prior to the .output one. + */ + 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"); + } + } else if (const Qubit matchingInputQubitForOutputLiteral = + userDefinedInputIdents.at(outputIoIdent); + 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 + */ + 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") { is.ignore(std::numeric_limits::max(), '\n'); continue; } else if (cmd == ".DEFINE") { @@ -177,60 +706,113 @@ 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; } - 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."); } std::string qubits; - std::string label; - getline(is, qubits); + if (!getline(is, qubits)) { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Failed read in gate definition"); + } + trimCommentAndTrailingWhitespaceData(qubits); - std::vector controls{}; - std::istringstream iss(qubits); + /* + * 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. + */ + 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(), ' ')); + } - // get controls and target - for (std::size_t i = 0; i < ncontrols; ++i) { - if (!(iss >> label)) { + // 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: Too few variables for gate " + m.str(1)); + "msg: SWAP gate is expected to operate on at least " + "two qubits but only " + + std::to_string(ncontrols) + " were defined"); } + ncontrols = static_cast(numberOfGateLines - 2); + } - const bool negativeControl = (label.at(0) == '-'); - if (negativeControl) { - label.erase(label.begin()); - } + 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( + line, numberOfGateLines, gateLines, validVariableIdentLookup, "-"); - auto iter = qregs.find(label); - if (iter == qregs.end()) { + std::size_t lineIdx = 0; + // get controls and target + for (std::size_t i = 0; i < ncontrols; ++i) { + 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(std::string(gateIdent), + qregs, ancregs); + controlLineQubit.has_value()) { + controls[i] = + Control(*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!"); } - 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 auto numberOfTargetLines = numberOfGateLines - ncontrols; + std::vector targetLineQubits(numberOfTargetLines, Qubit()); + for (std::size_t i = 0; i < numberOfTargetLines; ++i) { + 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(targetLineIdent, qregs, + ancregs); + targetLineQubit.has_value()) { + targetLineQubits[i] = *targetLineQubit; + } else { + throw QFRException("[real parser] l:" + std::to_string(line) + + " msg: Matching qubit for target line " + + targetLineIdent + " not found!"); + } } - const Qubit target = iter->second.first; switch (gate) { case I: case H: @@ -243,27 +825,34 @@ 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()}, + targetLineQubits.front(), gate); break; case X: - mcx(Controls{controls.cbegin(), controls.cend()}, target); + mcx(Controls{controls.cbegin(), controls.cend()}, + targetLineQubits.front()); break; case RX: case RY: case RZ: case P: emplace_back( - Controls{controls.cbegin(), controls.cend()}, target, gate, - std::vector{PI / (lambda)}); + 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, target, gate); + Controls{controls.cbegin(), controls.cend()}, target1, + targetLineQubits.front(), gate); break; } default: diff --git a/test/ir/test_real_parser.cpp b/test/ir/test_real_parser.cpp new file mode 100644 index 000000000..55db19734 --- /dev/null +++ b/test/ir/test_real_parser.cpp @@ -0,0 +1,1552 @@ +#include "Definitions.hpp" +#include "ir/Permutation.hpp" +#include "ir/QuantumComputation.hpp" + +#include "gmock/gmock-matchers.h" +#include +#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 << realHeaderVersionCommandPrefix << " " << std::fixed + << std::setprecision(1) << versionNumber << "\n"; + return *this; + } + + RealParserTest& usingNVariables(std::size_t numVariables) { + realFileContent << realHeaderNumVarsCommandPrefix << " " + << std::to_string(numVariables) << "\n"; + return *this; + } + + RealParserTest& usingVariables( + const std::initializer_list& variableIdents) { + return usingVariables(variableIdents, std::nullopt); + } + + 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) { + return usingInitialLayout(variableIdents, std::nullopt); + } + + 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) { + return usingInputs(inputIdents, std::nullopt); + } + + 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) { + return usingOutputs(outputIdents, std::nullopt); + } + + 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) { + return withConstants(constantValuePerVariable, std::nullopt); + } + + RealParserTest& + withConstants(const std::initializer_list& constantValuePerVariable, + const std::optional& optionalPostfix) { + const std::string concatenatedConstantValues(constantValuePerVariable); + pipeStringifiedCollectionToStream( + realFileContent, realHeaderConstantsCommandPrefix + " ", + {concatenatedConstantValues}, "", optionalPostfix); + return *this; + } + + RealParserTest& withGarbageValues( + const std::initializer_list& isGarbageValuePerVariable) { + return withGarbageValues(isGarbageValuePerVariable, std::nullopt); + } + + RealParserTest& withGarbageValues( + const std::initializer_list& isGarbageValuePerVariable, + const std::optional& optionalPostfix) { + + const std::string concatenatedIsGarbageValues(isGarbageValuePerVariable); + pipeStringifiedCollectionToStream( + realFileContent, realHeaderGarbageCommandPrefix + " ", + {concatenatedIsGarbageValues}, "", optionalPostfix); + return *this; + } + + RealParserTest& withEmptyGateList() { + realFileContent << realHeaderGateListPrefix << "\n" + << reakHeaderGateListPostfix; + return *this; + } + + RealParserTest& withGates( + const std::initializer_list& stringifiedGateList) { + if (stringifiedGateList.size() == 0) + return withEmptyGateList(); + + realFileContent << realHeaderGateListPrefix; + for (const auto& stringifiedGate : stringifiedGateList) + realFileContent << "\n" << stringifiedGate; + + realFileContent << "\n" << reakHeaderGateListPostfix; + return *this; + } + +protected: + 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"; + 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 constantValueZero = '0'; + const char constantValueOne = '1'; + const char constantValueNone = '-'; + + 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()); + } + + 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({qubit, qubit}); + } + return identityPermutation; + } + + 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"); + } + + static std::string + stringifyGate(const GateType gateType, + const std::initializer_list& controlLines, + const std::initializer_list& 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::optional& optionalPostfix) { + EXPECT_TRUE(targetLines.size() > static_cast(0)) + << "Gate must have at least one line defined"; + + std::stringstream stringifiedGateBuffer; + if (controlLines.size() == 0 && !optionalNumberOfGateLines.has_value()) + stringifiedGateBuffer << stringifyGateType(gateType); + else + stringifiedGateBuffer + << stringifyGateType(gateType) + << std::to_string(optionalNumberOfGateLines.value_or( + controlLines.size() + targetLines.size())); + + for (const auto& controlLine : controlLines) + stringifiedGateBuffer << " " << controlLine; + + for (const auto& targetLine : targetLines) + stringifiedGateBuffer << " " << targetLine; + + if (optionalPostfix.has_value()) + stringifiedGateBuffer << optionalPostfix.value(); + + return stringifiedGateBuffer.str(); + } +}; + +// 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({constantValueZero, constantValueZero, constantValueZero}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, MoreGarbageEntriesThanVariablesDeclared) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGarbageValues({isGarbageState, isNotGarbageState, isGarbageState}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + 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) + .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({constantValueNone}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, LessGarbageEntriesThanNumVariablesDeclared) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withGarbageValues({isNotGarbageState}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + 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, InvalidVariableIdentDefinition) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"variable-1", "v2"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, InvalidInputIdentDefinition) { + 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, InvalidInputIdentDefinitionInQuote) { + 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, InvalidVariableIdentDefinitionInInitialLayout) { + 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) + .usingVariables({"v1", "v2"}) + .usingInputs({"i1", "\"\""}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, InvalidOutputIdentDefinition) { + 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, InvalidOutputIdentDefinitionInQuote) { + 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, 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) + .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, DuplicateVariableIdentDefinition) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v1"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateInputIdentDefinition) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInputs({"i1", "i1"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateOutputIdentDefinition) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingOutputs({"o1", "o1"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + QFRException); +} + +TEST_F(RealParserTest, DuplicateVariableIdentDefinitionInInitialLayout) { + 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) + .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) + .usingVariables({"v1", "v2"}) + .withConstants({constantValueOne, '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', isNotGarbageState}); + + 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"}, std::nullopt)}); + + 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"}, std::nullopt)}); + + 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); +} + +TEST_F(RealParserTest, UnknownVariableIdentDefinitionInInitialLayout) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .usingInitialLayout({"v4", "v1"}) + .withEmptyGateList(); + + EXPECT_THROW( + quantumComputationInstance->import(realFileContent, Format::Real), + 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); +} + +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, NotDefinedVariableNotUsableAsTargetLine) { + 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) + .usingNVariables(2) + .usingVariables({"v1", "v2"}) + .withConstants({constantValueZero, constantValueNone}) + .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({constantValueNone, constantValueOne}) + .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({isNotGarbageState, isGarbageState}) + .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)); + + Permutation expectedOutputPermutation; + expectedOutputPermutation.emplace(static_cast(0), + static_cast(0)); + + ASSERT_EQ( + std::hash{}(expectedOutputPermutation), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +TEST_F(RealParserTest, InputIdentDefinitionInQuotes) { + 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, OutputIdentDefinitionInQuotes) { + 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\""}) + .withGarbageValues({isNotGarbageState, isGarbageState, isNotGarbageState, + isGarbageState}) + .usingOutputs({"i1", "o2", "i3", "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(4, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + 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{}(expectedOutputPermutation), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +TEST_F(RealParserTest, + InputIdentNotInQuotesAndMatchingOutputInQuotesNotConsideredEqual) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(4) + .usingVariables({"v1", "v2", "v3", "v4"}) + .usingInputs({"i1", "i2", "i3", "i4"}) + .withGarbageValues({isNotGarbageState, isGarbageState, isNotGarbageState, + isGarbageState}) + .usingOutputs({"i1", "\"i1\"", "i2", "\"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(4, quantumComputationInstance->getNqubits()); + ASSERT_EQ(0, quantumComputationInstance->getNancillae()); + ASSERT_EQ(2, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + 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), + 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({constantValueOne, constantValueNone, constantValueNone, + constantValueZero}) + .withGarbageValues({isGarbageState, isNotGarbageState, isNotGarbageState, + isGarbageState}) + .usingOutputs({"o1", "i1", "i4", "o2"}) + .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(false, true, true, false)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(true, false, false, true)); + + Permutation expectedOutputPermutation; + expectedOutputPermutation.emplace(static_cast(1), + static_cast(0)); + + expectedOutputPermutation.emplace(static_cast(2), + static_cast(3)); + + ASSERT_EQ( + std::hash{}(expectedOutputPermutation), + std::hash{}(quantumComputationInstance->outputPermutation)); +} + +TEST_F(RealParserTest, MatchingInputAndOutputInQuotes) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(4) + .usingVariables({"v1", "v2", "v3", "v4"}) + .usingInputs({"i1", "\"i2\"", "\"i3\"", "i4"}) + .withConstants({constantValueNone, constantValueOne, constantValueZero, + constantValueNone}) + .withGarbageValues({isNotGarbageState, isNotGarbageState, + isNotGarbageState, isGarbageState}) + .usingOutputs({"i4", "\"i3\"", "\"i2\"", "o1"}) + .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(1, quantumComputationInstance->getNgarbageQubits()); + ASSERT_THAT(quantumComputationInstance->garbage, + testing::ElementsAre(true, false, false, false)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(false, true, true, false)); + + Permutation expectedOutputPermutation; + expectedOutputPermutation.emplace(static_cast(0), + 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)); +} + +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(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(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)); +} + +TEST_F(RealParserTest, OutputPermutationForGarbageQubitsNotCreated) { + usingVersion(DEFAULT_REAL_VERSION) + .usingNVariables(4) + .usingVariables({"v1", "v2", "v3", "v4"}) + .usingInputs({"i1", "i2", "i3", "i4"}) + .withGarbageValues({isNotGarbageState, isGarbageState, isGarbageState, + isNotGarbageState}) + .usingOutputs({"i4", "o1", "o2", "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, true, true, false)); + ASSERT_THAT(quantumComputationInstance->ancillary, + testing::ElementsAre(false, false, false, false)); + + Permutation expectedOutputPermutation; + expectedOutputPermutation.emplace(static_cast(0), + static_cast(3)); + + expectedOutputPermutation.emplace(static_cast(3), + static_cast(0)); + + ASSERT_EQ( + 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()); + + const 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)); +} + +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()); +} + +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()); +}