diff --git a/include/algorithms/synthesis/dd_synthesis.hpp b/include/algorithms/synthesis/dd_synthesis.hpp index 76887f8c..2198d42e 100644 --- a/include/algorithms/synthesis/dd_synthesis.hpp +++ b/include/algorithms/synthesis/dd_synthesis.hpp @@ -2,6 +2,7 @@ #include "QuantumComputation.hpp" #include "algorithms/optimization/esop_minimization.hpp" +#include "algorithms/synthesis/encoding.hpp" #include "core/truthTable/truth_table.hpp" #include @@ -12,12 +13,12 @@ namespace syrec { class DDSynthesizer { public: - DDSynthesizer() = default; - - explicit DDSynthesizer(const std::size_t nqubits): - qc(nqubits) {} + static auto synthesize(const TruthTable& tt) -> std::shared_ptr { + DDSynthesizer synthesizer{}; + return synthesizer.synthesizeTT(tt); + } - auto synthesize(dd::mEdge src, std::unique_ptr>& dd) -> qc::QuantumComputation&; + auto synthesize(dd::mEdge src, std::unique_ptr>& dd) -> std::shared_ptr; [[nodiscard]] auto numGate() const -> std::size_t { return numGates; @@ -28,9 +29,21 @@ namespace syrec { } private: - double runtime = 0.; - std::size_t numGates = 0U; - qc::QuantumComputation qc; + double runtime = 0.; + std::size_t numGates = 0U; + std::unique_ptr> ddSynth; + std::shared_ptr qc; + + // n -> No. of primary inputs. + // m -> No. of primary outputs. + // totalNoBits -> Total no. of bits required to create the circuit + // r -> Additional variables/bits required to decode the output patterns. + + std::size_t n = 0U; + std::size_t m = 0U; + std::size_t totalNoBits = 0U; + std::size_t r = 0U; + bool garbageFlag = false; auto pathFromSrcDst(dd::mEdge const& src, dd::mNode* const& dst, TruthTable::Cube::Set& sigVec) const -> void; auto pathFromSrcDst(dd::mEdge const& src, dd::mNode* const& dst, TruthTable::Cube::Set& sigVec, TruthTable::Cube& cube) const -> void; @@ -56,6 +69,10 @@ namespace syrec { auto unifyPath(dd::mEdge src, dd::mEdge const& current, TruthTable::Cube::Set const& p1SigVec, TruthTable::Cube::Set const& p2SigVec, bool const& changePaths, std::unique_ptr>& dd) -> dd::mEdge; auto shiftingPaths(dd::mEdge const& src, dd::mEdge const& current, std::unique_ptr>& dd) -> dd::mEdge; + + auto decoder(TruthTable::CubeMap const& codewords) -> void; + + auto synthesizeTT(TruthTable tt) -> std::shared_ptr; }; } // namespace syrec diff --git a/include/algorithms/synthesis/encoding.hpp b/include/algorithms/synthesis/encoding.hpp index eb692a39..47945b2d 100644 --- a/include/algorithms/synthesis/encoding.hpp +++ b/include/algorithms/synthesis/encoding.hpp @@ -38,8 +38,8 @@ namespace syrec { auto extend(TruthTable& tt) -> void; - auto encodeHuffman(TruthTable& tt) -> void; + auto encodeHuffman(TruthTable& tt) -> std::pair; - auto augmentWithConstants(TruthTable& tt) -> void; + auto augmentWithConstants(TruthTable& tt, std::size_t const& nBits, bool dc = false) -> void; } //namespace syrec diff --git a/include/core/truthTable/truth_table.hpp b/include/core/truthTable/truth_table.hpp index 4c120f15..a5aa0ff2 100644 --- a/include/core/truthTable/truth_table.hpp +++ b/include/core/truthTable/truth_table.hpp @@ -239,9 +239,6 @@ namespace syrec { CubeMap cubeMap{}; public: - TruthTable() = default; - TruthTable(TruthTable& tt) = default; - auto operator==(const TruthTable& tt) const -> bool { return (cubeMap == tt.cubeMap); } diff --git a/src/algorithms/synthesis/dd_synthesis.cpp b/src/algorithms/synthesis/dd_synthesis.cpp index 27193d18..a2322781 100644 --- a/src/algorithms/synthesis/dd_synthesis.cpp +++ b/src/algorithms/synthesis/dd_synthesis.cpp @@ -1,5 +1,7 @@ #include "algorithms/synthesis/dd_synthesis.hpp" +#include "algorithms/synthesis/encoding.hpp" + using namespace dd::literals; namespace syrec { @@ -183,12 +185,9 @@ namespace syrec { const auto cubeSize = ctrlCube.size(); for (auto i = 0U; i < cubeSize; ++i) { if (ctrlCube[i].has_value()) { - const auto idx = static_cast(static_cast(current.p->v) - i - 1U); - if (*ctrlCube[i]) { - ctrl.emplace(dd::Control{idx, dd::Control::Type::pos}); - } else { - ctrl.emplace(dd::Control{idx, dd::Control::Type::neg}); - } + const auto idx = static_cast(static_cast(current.p->v) - i - 1U); + const auto ctrlType = *ctrlCube[i] ? dd::Control::Type::pos : dd::Control::Type::neg; + ctrl.emplace(dd::Control{idx, ctrlType}); } } } @@ -198,12 +197,9 @@ namespace syrec { const auto cubeSize = ctrlCube.size(); for (auto i = 0U; i < cubeSize; ++i) { if (ctrlCube[i].has_value()) { - const auto idx = static_cast((cubeSize - i) + static_cast(current.p->v)); - if (*ctrlCube[i]) { - ctrl.emplace(dd::Control{idx, dd::Control::Type::pos}); - } else { - ctrl.emplace(dd::Control{idx, dd::Control::Type::neg}); - } + const auto idx = static_cast((cubeSize - i) + static_cast(current.p->v)); + const auto ctrlType = *ctrlCube[i] ? dd::Control::Type::pos : dd::Control::Type::neg; + ctrl.emplace(dd::Control{idx, ctrlType}); } } } @@ -232,7 +228,7 @@ namespace syrec { to = tmp; dd->garbageCollect(); - qc.emplace_back(op); + qc->emplace_back(op); ++numGates; } @@ -372,11 +368,10 @@ namespace syrec { for (auto const& rootVec: rootSolution) { dd::Controls ctrlFinal; controlRoot(current, ctrlFinal, rootVec); - if (!changePaths) { - ctrlFinal.emplace(dd::Control{current.p->v, dd::Control::Type::pos}); - } else { - ctrlFinal.emplace(dd::Control{current.p->v, dd::Control::Type::neg}); - } + + const auto ctrlType = changePaths ? dd::Control::Type::neg : dd::Control::Type::pos; + ctrlFinal.emplace(dd::Control{current.p->v, ctrlType}); + ctrlFinal.insert(ctrlNonRoot.begin(), ctrlNonRoot.end()); for (std::size_t i = 0; i < targetSize; ++i) { @@ -433,21 +428,83 @@ namespace syrec { return unifyPath(src, current, p1SigVec, p2SigVec, changePaths, dd); } - // This function returns the operations required to synthesize the DD. - auto DDSynthesizer::synthesize(dd::mEdge src, std::unique_ptr>& dd) -> qc::QuantumComputation& { - qc.clear(); - runtime = 0.; - numGates = 0U; + // Refer to the decoder algorithm of https://www.cda.cit.tum.de/files/eda/2018_aspdac_coding_techniques_in_synthesis.pdf. + auto DDSynthesizer::decoder(TruthTable::CubeMap const& codewords) -> void { + const auto codeLength = codewords.begin()->second.size(); + + // decode the r most significant bits of the original output pattern. + if (r != 0U) { + for (const auto& [pattern, code]: codewords) { + TruthTable::Cube targetCube(pattern.begin(), pattern.begin() + static_cast(r)); + + dd::Controls ctrl; + for (auto i = 0U; i < codeLength; i++) { + if (code[i].has_value()) { + const auto ctrlType = *code[i] ? dd::Control::Type::pos : dd::Control::Type::neg; + ctrl.emplace(dd::Control{static_cast((codeLength - 1U) - i), ctrlType}); + } + } + + const auto targetSize = targetCube.size(); + + for (std::size_t i = 0U; i < targetSize; ++i) { + if (targetCube[i].has_value() && *(targetCube[i])) { + const auto targetBit = static_cast((totalNoBits - 1U) - i); + qc->x(targetBit, ctrl); + ++numGates; + } + } + } + } + + // decode the remaining (m − r) primary outputs. + const auto correctionBits = m - r; + + if (correctionBits == 0U) { + return; + } + + TruthTable ttCorrection{}; + + for (const auto& [pattern, code]: codewords) { + TruthTable::Cube outCube(pattern); + outCube.resize(totalNoBits); + + TruthTable::Cube inCube(pattern.begin(), pattern.begin() + static_cast(r)); + for (auto i = 0U; i < codeLength; i++) { + inCube.emplace_back(code[i]); + } + + // Extend the dc in the inputs. + const auto completeInputs = inCube.completeCubes(); + for (auto const& completeInput: completeInputs) { + ttCorrection.try_emplace(completeInput, outCube); + } + } + + const auto ttCorrectionDD = buildDD(ttCorrection, ddSynth); + garbageFlag = true; + synthesize(ttCorrectionDD, ddSynth); + } + // This function returns the operations required to synthesize the DD. + auto DDSynthesizer::synthesize(dd::mEdge src, std::unique_ptr>& dd) -> std::shared_ptr { if (src.p == nullptr || src.p->isIdentity() || dcNodeCondition(src)) { return qc; } + totalNoBits = static_cast(src.p->v + 1); + + if (!garbageFlag) { + qc = std::make_shared(totalNoBits); + } + + // The threshold after which the outputs are considered to be garbage. + const auto garbageThreshold = static_cast(totalNoBits) - static_cast(m); + // This following ensures that the `src` node resembles an identity structure. // Refer to algorithm Q of http://www.informatik.uni-bremen.de/agra/doc/konf/12aspdac_qmdd_synth_rev.pdf. - const auto start = std::chrono::steady_clock::now(); - // to preserve the `src` DD throughout the synthesis, its reference count has to be at least 2. while (src.p->ref < 2U) { dd->incRef(src); @@ -460,9 +517,17 @@ namespace syrec { // set of nodes that have already been processed. std::unordered_set visited{}; + const auto start = std::chrono::steady_clock::now(); + // while there are nodes left to process. while (!queue.empty()) { const auto current = queue.front(); + + // if the garbageFlag is true, the synthesis is terminated once the garbage threshold is reached. + if (garbageFlag && current.p->v == garbageThreshold - 1) { + break; + } + queue.pop(); if (current.p == nullptr || current.p->isIdentity() || dcNodeCondition(current)) { @@ -506,7 +571,42 @@ namespace syrec { } } runtime = static_cast((std::chrono::steady_clock::now() - start).count()); + return qc; + } + + auto DDSynthesizer::synthesizeTT(TruthTable tt) -> std::shared_ptr { + runtime = 0.; + numGates = 0U; + garbageFlag = false; + + n = tt.nInputs(); + m = tt.nOutputs(); + + extend(tt); + + // codewords -> Output patterns with the respective codewords. + // k1 -> Minimum no. of additional lines required. + const auto [codewords, k1] = encodeHuffman(tt); + + totalNoBits = std::max(n, m + k1); + r = totalNoBits - tt.nOutputs(); + + augmentWithConstants(tt, totalNoBits); + ddSynth = std::make_unique>(totalNoBits); + + const auto src = buildDD(tt, ddSynth); + + const auto start = std::chrono::steady_clock::now(); + synthesize(src, ddSynth); + + // if codeword is not empty, the above synthesized encoded function should be decoded. + if (!codewords.empty()) { + // synthesizing the corresponding decoder circuit. + decoder(codewords); + } + + runtime = static_cast((std::chrono::steady_clock::now() - start).count()); return qc; } diff --git a/src/algorithms/synthesis/encoding.cpp b/src/algorithms/synthesis/encoding.cpp index 7e4252bd..23815808 100644 --- a/src/algorithms/synthesis/encoding.cpp +++ b/src/algorithms/synthesis/encoding.cpp @@ -40,7 +40,7 @@ namespace syrec { } } - auto encodeHuffman(TruthTable& tt) -> void { + auto encodeHuffman(TruthTable& tt) -> std::pair { std::map outputFreq; for (const auto& [input, output]: tt) { outputFreq[output]++; @@ -48,7 +48,7 @@ namespace syrec { // if the truth table function is already reversible, no encoding is necessary if (outputFreq.size() == tt.size()) { - return; + return {{}, 0U}; } // create a priority queue for building the Huffman tree @@ -60,12 +60,19 @@ namespace syrec { decltype(comp)> minHeap(comp); + std::unordered_set freqSet; + freqSet.reserve(outputFreq.size()); + // initialize the leaves of the Huffman tree from the output frequencies for (const auto& [output, freq]: outputFreq) { const auto requiredGarbage = static_cast(std::ceil(std::log2(freq))); + freqSet.emplace(requiredGarbage); minHeap.emplace(std::make_shared(output, requiredGarbage)); } + // Minimum no. of additional lines required. + const auto additionalLines = *std::max_element(freqSet.begin(), freqSet.end()); + // combine the nodes with the smallest weights until there is only one node left while (minHeap.size() > 1U) { // pop the two nodes with the smallest weights @@ -84,6 +91,9 @@ namespace syrec { } const auto requiredGarbage = minHeap.top()->freq; + const auto nBits = std::max(tt.nInputs(), tt.nOutputs() + additionalLines); + const auto r = nBits - requiredGarbage; + // determine encoding from Huffman tree TruthTable::CubeMap encoding{}; minHeap.top()->traverse({}, encoding); @@ -93,14 +103,44 @@ namespace syrec { output.resize(requiredGarbage); } + // modify the codewords by filling in the redundant dc positions. + for (auto& [pattern, code]: encoding) { + TruthTable::Cube outCube(pattern); + outCube.resize(nBits); + + TruthTable::Cube newCode{}; + newCode.reserve(requiredGarbage); + for (auto i = 0U; i < requiredGarbage; i++) { + if (code[i].has_value()) { + newCode.emplace_back(code[i]); + } else { + newCode.emplace_back(outCube[r + i]); + } + } + + encoding[pattern] = newCode; + } + // encode all the outputs for (auto& [input, output]: tt) { output = encoding[output]; } + + return {encoding, additionalLines}; } - auto augmentWithConstants(TruthTable& tt) -> void { - for (auto const& [input, output]: tt) { + auto augmentWithConstants(TruthTable& tt, std::size_t const& nBits, bool dc) -> void { + const auto ancillaBits = nBits - tt.nOutputs(); + + for (auto& [input, output]: tt) { + // add necessary constant inputs to the outputs based on the total number of bits (nBits). + if (!dc) { + for (auto i = 0U; i < ancillaBits; i++) { + output.insertZero(); + } + } else { + output.resize(nBits); + } const auto inputSize = input.size(); const auto outputSize = output.size(); if (inputSize >= outputSize) { diff --git a/test/unittests/test_dd_synthesis.cpp b/test/unittests/test_dd_synthesis.cpp index 2ce6a0ca..2a0e5558 100644 --- a/test/unittests/test_dd_synthesis.cpp +++ b/test/unittests/test_dd_synthesis.cpp @@ -60,10 +60,10 @@ TEST_P(TestDDSynth, GenericDDSynthesisTest) { const auto ttDD = buildDD(tt, dd); EXPECT_TRUE(ttDD.p != nullptr); + DDSynthesizer synthesizer{}; - DDSynthesizer synthesizer(tt.nInputs()); - const auto& qc = synthesizer.synthesize(ttDD, dd); - const auto& qcDD = dd::buildFunctionality(&qc, dd); + const auto qc = synthesizer.synthesize(ttDD, dd); + const auto& qcDD = dd::buildFunctionality(qc.get(), dd); EXPECT_EQ(ttDD, qcDD); std::cout << synthesizer.numGate() << "\n"; diff --git a/test/unittests/test_dd_synthesis_dc.cpp b/test/unittests/test_dd_synthesis_dc.cpp index 74b16ef6..8f7eb0bf 100644 --- a/test/unittests/test_dd_synthesis_dc.cpp +++ b/test/unittests/test_dd_synthesis_dc.cpp @@ -10,11 +10,10 @@ using namespace syrec; class TestDDSynthDc: public testing::TestWithParam { protected: - TruthTable tt{}; - TruthTable ttqc{}; - std::string testCircuitsDir = "./circuits/"; - std::unique_ptr> dd = std::make_unique>(15U); - std::string fileName; + TruthTable tt{}; + TruthTable ttqc{}; + std::string testCircuitsDir = "./circuits/"; + std::string fileName; void SetUp() override { fileName = testCircuitsDir + GetParam() + ".pla"; @@ -38,35 +37,20 @@ INSTANTIATE_TEST_SUITE_P(TestDDSynth, TestDDSynthDc, "rd32_19", "c17", "con1", - "root", "sqr", - "sqrt8", - "life", "aludc", "minialu", - "dc2", - "dk27", - "pm1", "majority", - "max10", "4gt10", "counter", "4gt5", "4mod5", "decode24", - "mod10", - "asym", "decode24e", - "radd", - "radd8", "rd53", - "rd84", - "dist", - "mlp4", "wim", "z4", - "z4ml", - "misex"), + "z4ml"), [](const testing::TestParamInfo& info) { auto s = info.param; std::replace( s.begin(), s.end(), '-', '_'); @@ -74,22 +58,17 @@ INSTANTIATE_TEST_SUITE_P(TestDDSynth, TestDDSynthDc, TEST_P(TestDDSynthDc, GenericDDSynthesisDcTest) { EXPECT_TRUE(readPla(tt, fileName)); - extend(tt); - - encodeHuffman(tt); - augmentWithConstants(tt); + const auto& qc = DDSynthesizer::synthesize(tt); + const auto totalNoBits = qc->getNqubits(); - const auto ttDD = buildDD(tt, dd); - EXPECT_TRUE(ttDD.p != nullptr); - - DDSynthesizer synthesizer(tt.nInputs()); - const auto& qc = synthesizer.synthesize(ttDD, dd); + // generate the complete truth table. + extend(tt); + augmentWithConstants(tt, totalNoBits, true); - buildTruthTable(qc, ttqc); + buildTruthTable(*qc, ttqc); EXPECT_TRUE(TruthTable::equal(tt, ttqc)); - std::cout << synthesizer.numGate() << "\n"; - std::cout << synthesizer.getExecutionTime() << "\n"; + std::cout << qc->getNops() << "\n"; } diff --git a/test/unittests/test_huffman.cpp b/test/unittests/test_huffman.cpp index 1259eb93..e653681e 100644 --- a/test/unittests/test_huffman.cpp +++ b/test/unittests/test_huffman.cpp @@ -121,14 +121,14 @@ TEST_F(TestHuff, HUFF2) { EXPECT_EQ(tt.nInputs(), 2U); - augmentWithConstants(tt); + augmentWithConstants(tt, 4U); - EXPECT_EQ(tt.nInputs(), 3U); + EXPECT_EQ(tt.nInputs(), 4U); - const std::vector augInput{0b000U, 0b001U, 0b010U, 0b011U}; + const std::vector augInput{0b0000U, 0b0001U, 0b0010U, 0b0011U}; for (const auto& in2: augInput) { - auto search4 = tt.find(in2, 3U); + auto search4 = tt.find(in2, 4U); EXPECT_TRUE(search4 != tt.end()); }