Unverified Commit f8be3b19 authored by Mccaskey, Alex's avatar Mccaskey, Alex Committed by GitHub

Merge pull request #240 from tnguyen-ornl/tnguyen/circuit-comp

Added a util function to determine a common ansatz among a group of Composite Instructions
parents 24fe6037 c8454c66
Pipeline #113256 passed with stage
in 15 minutes and 37 seconds
/*******************************************************************************
* Copyright (c) 2020 UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompanies this
* distribution. The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Eclipse Distribution
*License is available at https://eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Thien Nguyen - initial API and implementation
*******************************************************************************/
#include "IRUtils.hpp"
#include "CompositeInstruction.hpp"
#include "InstructionIterator.hpp"
#include "xacc_service.hpp"
#include "IRProvider.hpp"
#include <cassert>
namespace {
// Computes the base length of a Composite:
// i.e. not include measure gates.
size_t
getBaseLength(const std::shared_ptr<xacc::CompositeInstruction> &in_composite) {
size_t length = 0;
xacc::InstructionIterator it(in_composite);
while (it.hasNext()) {
auto nextInst = it.next();
if (nextInst->isEnabled() && !nextInst->isComposite()) {
if (nextInst->name() == "Measure") {
return length;
}
length++;
}
}
return length;
}
bool compareInst(xacc::InstPtr in_a, xacc::InstPtr in_b) {
if (!in_a || !in_b) {
return false;
}
// We should only compare elementary instructions.
assert(!in_a->isComposite() && !in_b->isComposite());
assert(in_a->isEnabled() && in_b->isEnabled());
return (in_a->name() == in_b->name()) && (in_a->bits() == in_b->bits()) &&
(in_a->getParameters() == in_b->getParameters());
}
// Helper to pop the instruction stack (so that we can walk both trees
// simultaneously)
xacc::InstPtr getNextInstruction(xacc::InstructionIterator &iter) {
while (iter.hasNext()) {
auto nextInst = iter.next();
if (nextInst->isEnabled() && !nextInst->isComposite()) {
return nextInst;
}
}
return nullptr;
}
} // namespace
namespace xacc {
namespace quantum {
ObservedAnsatz ObservedAnsatz::fromObservedComposites(
const std::vector<std::shared_ptr<CompositeInstruction>> &in_composites) {
auto gateRegistry = xacc::getService<xacc::IRProvider>("quantum");
auto baseComposite = gateRegistry->createComposite("__BASE_COMPOSITE__");
ObservedAnsatz result;
// If there is only one composite.
if (in_composites.size() == 1) {
// Just use an empty base,
// i.e. the only circuit is the observable circuit.
result.m_baseAnsatz = baseComposite;
result.m_obsCircuits = {in_composites[0]};
return result;
}
std::shared_ptr<CompositeInstruction> shortestComposite = in_composites[0];
// Find the shortest composite (not include the measure part)
for (size_t i = 1; i < in_composites.size(); ++i) {
if (getBaseLength(in_composites[i]) < getBaseLength(shortestComposite)) {
shortestComposite = in_composites[i];
}
}
InstructionIterator it(shortestComposite);
while (it.hasNext()) {
auto nextInst = it.next();
if (nextInst->isEnabled() && !nextInst->isComposite()) {
if (nextInst->name() == "Measure") {
break;
}
baseComposite->addInstruction(nextInst->clone());
}
}
// Check if a composite is the base of the other
const auto isBaseOf =
[](const std::shared_ptr<CompositeInstruction> &in_base,
const std::shared_ptr<CompositeInstruction> &in_toTest,
InstructionIterator &out_Iter) {
assert(getBaseLength(in_base) <= getBaseLength(in_toTest));
bool result = true;
InstructionIterator baseIt(in_base);
InstructionIterator otherIter(in_toTest);
while (baseIt.hasNext()) {
auto nextInst = baseIt.next();
if (nextInst->isEnabled() && !nextInst->isComposite()) {
// Pop the other iter as well
auto otherInstr = getNextInstruction(otherIter);
if (!compareInst(nextInst, otherInstr)) {
return false;
}
out_Iter = otherIter;
}
}
return result;
};
const std::function<InstPtr(std::shared_ptr<CompositeInstruction>)>
getLastInstr =
[&](std::shared_ptr<CompositeInstruction> composite) -> InstPtr {
auto instructions = composite->getInstructions();
for (auto rIter = instructions.rbegin(); rIter != instructions.rend();
++rIter) {
if ((*rIter)->isEnabled()) {
if (!(*rIter)->isComposite()) {
return (*rIter);
} else {
std::shared_ptr<CompositeInstruction> compositeCast =
std::dynamic_pointer_cast<CompositeInstruction>(*rIter);
return getLastInstr(compositeCast);
}
return (*rIter);
}
}
return nullptr;
};
// Cache of the iterators during matching
std::vector<InstructionIterator> matchedIters;
while (getBaseLength(baseComposite) > 1) {
bool allMatched = true;
matchedIters.clear();
for (const auto &compositeToTest : in_composites) {
InstructionIterator compIter(compositeToTest);
if (!isBaseOf(baseComposite, compositeToTest, compIter)) {
// Remove the last instruction of base and re-check
auto lastInst = getLastInstr(baseComposite);
if (lastInst) {
lastInst->disable();
baseComposite->removeDisabled();
} else {
baseComposite->clear();
}
allMatched = false;
break;
}
matchedIters.emplace_back(compIter);
}
if (allMatched) {
assert(matchedIters.size() == in_composites.size());
std::vector<std::shared_ptr<CompositeInstruction>> subCircuits;
for (size_t i = 0; i < in_composites.size(); ++i) {
auto comp = in_composites[i];
auto iter = matchedIters[i];
auto newComp =
gateRegistry->createComposite("__OBSERVED__" + comp->name());
// Add the remaining instructions
while (iter.hasNext()) {
auto nextInst = iter.next();
if (nextInst->isEnabled() && !nextInst->isComposite()) {
newComp->addInstruction(nextInst->clone());
}
}
subCircuits.emplace_back(newComp);
}
result.m_baseAnsatz = baseComposite;
result.m_obsCircuits = subCircuits;
return result;
}
}
// These circuits have no relationship:
baseComposite->clear();
// Returns an empty base and all input composites as individual circuits.
result.m_baseAnsatz = baseComposite;
result.m_obsCircuits = in_composites;
return result;
}
bool ObservedAnsatz::validate(
const std::vector<std::shared_ptr<CompositeInstruction>> &in_composites)
const {
if (m_obsCircuits.size() != in_composites.size()) {
return false;
}
auto gateRegistry = xacc::getService<xacc::IRProvider>("quantum");
for (int i = 0; i < m_obsCircuits.size(); ++i) {
auto obsCirc = m_obsCircuits[i];
std::shared_ptr<xacc::CompositeInstruction> combinedComp =
gateRegistry->createComposite("__RECOVERED__" + std::to_string(i));
for (int idx = 0; idx < m_baseAnsatz->nInstructions(); ++idx) {
combinedComp->addInstruction(m_baseAnsatz->getInstruction(idx)->clone());
}
for (int idx = 0; idx < obsCirc->nInstructions(); ++idx) {
combinedComp->addInstruction(obsCirc->getInstruction(idx)->clone());
}
auto originalCirc = in_composites[i];
// Compare two composites:
InstructionIterator origIter(originalCirc);
InstructionIterator otherIter(combinedComp);
while (origIter.hasNext()) {
auto nextInst = origIter.next();
if (nextInst->isEnabled() && !nextInst->isComposite()) {
// Pop the other iter as well
auto otherInstr = getNextInstruction(otherIter);
if (!compareInst(nextInst, otherInstr)) {
return false;
}
}
}
assert(!origIter.hasNext());
if (otherIter.hasNext()) {
// The other composite contains extra instructions.
return false;
}
}
return true;
}
} // namespace quantum
} // namespace xacc
\ No newline at end of file
/*******************************************************************************
* Copyright (c) 2020 UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompanies this
* distribution. The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Eclipse Distribution
*License is available at https://eclipse.org/org/documents/edl-v10.php
*
* Contributors:
* Thien Nguyen - initial API and implementation
*******************************************************************************/
#pragma once
#include <vector>
#include <memory>
namespace xacc {
class CompositeInstruction;
namespace quantum {
// Describes a common pattern whereby there is
// a common base ansatz/state-preparation circuit (no measurement)
// and a set of *observe* sub-circuits appended to that base circuit.
class ObservedAnsatz {
public:
[[nodiscard]] static ObservedAnsatz fromObservedComposites(
const std::vector<std::shared_ptr<CompositeInstruction>> &in_composites);
std::shared_ptr<CompositeInstruction> getBase() const { return m_baseAnsatz; }
std::vector<std::shared_ptr<CompositeInstruction>>
getObservedSubCircuits() const {
return m_obsCircuits;
}
// Helper for users to verify that the decomposition indeed valid
bool validate(const std::vector<std::shared_ptr<CompositeInstruction>>
&in_composites) const;
private:
std::shared_ptr<CompositeInstruction> m_baseAnsatz;
std::vector<std::shared_ptr<CompositeInstruction>> m_obsCircuits;
};
} // namespace quantum
} // namespace xacc
\ No newline at end of file
......@@ -22,6 +22,9 @@ include_directories(${CMAKE_SOURCE_DIR}/xacc/utils)
add_xacc_test(AllGateVisitor)
add_xacc_test(JsonVisitor)
add_xacc_test(IRToGraphVisitor)
add_xacc_test(IRUtils)
target_link_libraries(IRToGraphVisitorTester xacc-quantum-gate)
target_link_libraries(JsonVisitorTester xacc-quantum-gate Boost::graph)
target_link_libraries(AllGateVisitorTester xacc-quantum-gate Boost::graph)
target_link_libraries(IRUtilsTester xacc-quantum-gate)
#include <gtest/gtest.h>
#include "CommonGates.hpp"
#include "InstructionIterator.hpp"
#include "xacc.hpp"
#include "xacc_service.hpp"
#include <random>
#include <iterator>
#include "IRUtils.hpp"
#include "xacc_observable.hpp"
using namespace xacc::quantum;
namespace {
template <typename Iter, typename RandomGenerator>
Iter selectRandomly(Iter start, Iter end, RandomGenerator &g) {
std::uniform_int_distribution<> dis(0, std::distance(start, end) - 1);
std::advance(start, dis(g));
return start;
}
template <typename Iter> Iter selectRandomly(Iter start, Iter end) {
static std::random_device rd;
static std::mt19937 gen(rd());
return selectRandomly(start, end, gen);
}
} // namespace
TEST(IRUtilsTester, checkSimple) {
auto base = std::make_shared<Circuit>("base");
auto x = std::make_shared<X>(0);
auto h = std::make_shared<Hadamard>(1);
auto cn1 = std::make_shared<CNOT>(1, 2);
auto rz = std::make_shared<Rz>(1, 3.1415);
auto z = std::make_shared<Z>(2);
base->addInstruction(x);
base->addInstruction(h);
base->addInstruction(cn1);
base->addInstruction(rz);
base->addInstruction(z);
std::vector<std::shared_ptr<xacc::quantum::Gate>> InstructionSet{h, x, z, rz};
auto measure = std::make_shared<Measure>(1);
const int NB_CIRCUITS = 10;
std::vector<std::shared_ptr<xacc::CompositeInstruction>> testCircs;
auto gateRegistry = xacc::getService<xacc::IRProvider>("quantum");
for (int i = 0; i < NB_CIRCUITS; ++i) {
std::shared_ptr<xacc::CompositeInstruction> newComp =
gateRegistry->createComposite("__COMPOSITE__" + std::to_string(i));
for (int idx = 0; idx < base->nInstructions(); ++idx) {
newComp->addInstruction(base->getInstruction(idx)->clone());
}
// Add a random gate to the base circuit
auto randomGate =
selectRandomly(InstructionSet.begin(), InstructionSet.end());
newComp->addInstruction((*randomGate)->clone());
newComp->addInstruction(measure->clone());
testCircs.emplace_back(newComp);
}
for (const auto &circ : testCircs) {
std::cout << "HOWDY: \n" << circ->toString() << "\n";
}
auto result = ObservedAnsatz::fromObservedComposites(testCircs);
EXPECT_EQ(result.getBase()->nInstructions(), base->nInstructions());
EXPECT_EQ(result.getBase()->toString(), base->toString());
EXPECT_TRUE(result.validate(testCircs));
}
TEST(IRUtilsTester, checkObservable) {
{
auto H_N_2 = xacc::quantum::getObservable(
"pauli", std::string("5.907 - 2.1433 X0X1 "
"- 2.1433 Y0Y1"
"+ .21829 Z0 - 6.125 Z1"));
xacc::qasm(R"(
.compiler xasm
.circuit deuteron_ansatz
.parameters theta
.qbit q
X(q[0]);
Ry(q[1], theta);
CNOT(q[1],q[0]);
)");
auto ansatz = xacc::getCompiled("deuteron_ansatz");
auto evaled = ansatz->operator()({1.2345});
const auto fsToExe = H_N_2->observe(evaled);
auto result = ObservedAnsatz::fromObservedComposites(fsToExe);
EXPECT_EQ(result.getBase()->toString(), evaled->toString());
EXPECT_TRUE(result.validate(fsToExe));
}
{
auto H_N_3 = xacc::quantum::getObservable(
"pauli",
std::string(
"5.907 - 2.1433 X0X1 - 2.1433 Y0Y1 + .21829 Z0 - 6.125 Z1 + "
"9.625 - 9.625 Z2 - 3.91 X1 X2 - 3.91 Y1 Y2"));
xacc::qasm(R"(
.compiler xasm
.circuit deuteron_ansatz_h3
.parameters t0, t1
.qbit q
X(q[0]);
exp_i_theta(q, t0, {{"pauli", "X0 Y1 - Y0 X1"}});
exp_i_theta(q, t1, {{"pauli", "X0 Z1 Y2 - X2 Z1 Y0"}});
)");
auto ansatz = xacc::getCompiled("deuteron_ansatz_h3");
auto evaled = ansatz->operator()({1.2345, 6.789});
const auto fsToExe = H_N_3->observe(evaled);
auto result = ObservedAnsatz::fromObservedComposites(fsToExe);
EXPECT_EQ(result.getBase()->toString(), evaled->toString());
EXPECT_TRUE(result.validate(fsToExe));
}
}
int main(int argc, char **argv) {
xacc::Initialize(argc, argv);
::testing::InitGoogleTest(&argc, argv);
auto ret = RUN_ALL_TESTS();
xacc::Finalize();
return ret;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment