Skip to content

Commit

Permalink
Decoding the irreversible function. (#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
SmaranTum authored Dec 6, 2022
1 parent 8cc2c3a commit ff2ca63
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 82 deletions.
33 changes: 25 additions & 8 deletions include/algorithms/synthesis/dd_synthesis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <chrono>
Expand All @@ -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<qc::QuantumComputation> {
DDSynthesizer synthesizer{};
return synthesizer.synthesizeTT(tt);
}

auto synthesize(dd::mEdge src, std::unique_ptr<dd::Package<>>& dd) -> qc::QuantumComputation&;
auto synthesize(dd::mEdge src, std::unique_ptr<dd::Package<>>& dd) -> std::shared_ptr<qc::QuantumComputation>;

[[nodiscard]] auto numGate() const -> std::size_t {
return numGates;
Expand All @@ -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<dd::Package<>> ddSynth;
std::shared_ptr<qc::QuantumComputation> 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;
Expand All @@ -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::Package<>>& dd) -> dd::mEdge;
auto shiftingPaths(dd::mEdge const& src, dd::mEdge const& current, std::unique_ptr<dd::Package<>>& dd) -> dd::mEdge;

auto decoder(TruthTable::CubeMap const& codewords) -> void;

auto synthesizeTT(TruthTable tt) -> std::shared_ptr<qc::QuantumComputation>;
};

} // namespace syrec
4 changes: 2 additions & 2 deletions include/algorithms/synthesis/encoding.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ namespace syrec {

auto extend(TruthTable& tt) -> void;

auto encodeHuffman(TruthTable& tt) -> void;
auto encodeHuffman(TruthTable& tt) -> std::pair<TruthTable::CubeMap, std::size_t>;

auto augmentWithConstants(TruthTable& tt) -> void;
auto augmentWithConstants(TruthTable& tt, std::size_t const& nBits, bool dc = false) -> void;

} //namespace syrec
3 changes: 0 additions & 3 deletions include/core/truthTable/truth_table.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
150 changes: 125 additions & 25 deletions src/algorithms/synthesis/dd_synthesis.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "algorithms/synthesis/dd_synthesis.hpp"

#include "algorithms/synthesis/encoding.hpp"

using namespace dd::literals;

namespace syrec {
Expand Down Expand Up @@ -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<dd::Qubit>(static_cast<std::size_t>(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<dd::Qubit>(static_cast<std::size_t>(current.p->v) - i - 1U);
const auto ctrlType = *ctrlCube[i] ? dd::Control::Type::pos : dd::Control::Type::neg;
ctrl.emplace(dd::Control{idx, ctrlType});
}
}
}
Expand All @@ -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<dd::Qubit>((cubeSize - i) + static_cast<std::size_t>(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<dd::Qubit>((cubeSize - i) + static_cast<std::size_t>(current.p->v));
const auto ctrlType = *ctrlCube[i] ? dd::Control::Type::pos : dd::Control::Type::neg;
ctrl.emplace(dd::Control{idx, ctrlType});
}
}
}
Expand Down Expand Up @@ -232,7 +228,7 @@ namespace syrec {
to = tmp;
dd->garbageCollect();

qc.emplace_back(op);
qc->emplace_back(op);
++numGates;
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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::Package<>>& 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<int>(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<dd::Qubit>((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<dd::Qubit>((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<int>(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::Package<>>& dd) -> std::shared_ptr<qc::QuantumComputation> {
if (src.p == nullptr || src.p->isIdentity() || dcNodeCondition(src)) {
return qc;
}

totalNoBits = static_cast<std::size_t>(src.p->v + 1);

if (!garbageFlag) {
qc = std::make_shared<qc::QuantumComputation>(totalNoBits);
}

// The threshold after which the outputs are considered to be garbage.
const auto garbageThreshold = static_cast<dd::Qubit>(totalNoBits) - static_cast<dd::Qubit>(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);
Expand All @@ -460,9 +517,17 @@ namespace syrec {
// set of nodes that have already been processed.
std::unordered_set<dd::mEdge> 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)) {
Expand Down Expand Up @@ -506,7 +571,42 @@ namespace syrec {
}
}
runtime = static_cast<double>((std::chrono::steady_clock::now() - start).count());
return qc;
}

auto DDSynthesizer::synthesizeTT(TruthTable tt) -> std::shared_ptr<qc::QuantumComputation> {
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<dd::Package<>>(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<double>((std::chrono::steady_clock::now() - start).count());
return qc;
}

Expand Down
48 changes: 44 additions & 4 deletions src/algorithms/synthesis/encoding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ namespace syrec {
}
}

auto encodeHuffman(TruthTable& tt) -> void {
auto encodeHuffman(TruthTable& tt) -> std::pair<TruthTable::CubeMap, std::size_t> {
std::map<TruthTable::Cube, std::size_t> outputFreq;
for (const auto& [input, output]: tt) {
outputFreq[output]++;
}

// 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
Expand All @@ -60,12 +60,19 @@ namespace syrec {
decltype(comp)>
minHeap(comp);

std::unordered_set<std::size_t> 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::size_t>(std::ceil(std::log2(freq)));
freqSet.emplace(requiredGarbage);
minHeap.emplace(std::make_shared<MinHeapNode>(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
Expand All @@ -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);
Expand All @@ -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) {
Expand Down
Loading

0 comments on commit ff2ca63

Please sign in to comment.