Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Decoding the irreversible function. #120

Merged
merged 7 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
burgholzer marked this conversation as resolved.
Show resolved Hide resolved
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, std::unique_ptr<dd::Package<>>& dd) -> void;
burgholzer marked this conversation as resolved.
Show resolved Hide resolved

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, std::unique_ptr<dd::Package<>>& dd) -> 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});
burgholzer marked this conversation as resolved.
Show resolved Hide resolved
}
}

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, dd);
garbageFlag = true;
synthesize(ttCorrectionDD, dd);
}

// 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, ddSynth);
}

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