...
 
Commits (4)
......@@ -29,18 +29,17 @@
*
**********************************************************************************/
#include "TNQVM.hpp"
#include "PauliOperator.hpp"
#include "IRUtils.hpp"
namespace {
inline int getShotCountOption(const xacc::HeterogeneousMap& in_options)
{
inline int getShotCountOption(const xacc::HeterogeneousMap &in_options) {
int result = -1;
if (in_options.keyExists<int>("shots")) {
result = in_options.get<int>("shots");
}
return result;
}
return result;
}
} // namespace
namespace tnqvm {
const std::string TNQVM::DEFAULT_VISITOR_BACKEND = "itensor-mps";
......@@ -48,20 +47,19 @@ const std::string TNQVM::DEFAULT_VISITOR_BACKEND = "itensor-mps";
void TNQVM::execute(
std::shared_ptr<AcceleratorBuffer> buffer,
const std::vector<std::shared_ptr<xacc::CompositeInstruction>> functions) {
if (vqeMode && functions[0]->getInstruction(0)->isComposite()) {
// Here we assume we have one ansatz function,
// functions[0]->getInstruction(0)
visitor = xacc::getService<TNQVMVisitor>(getVisitorName())->clone();
visitor = xacc::getService<TNQVMVisitor>(getVisitorName())->clone();
// If in VQE mode and there are more than one kernels
if (vqeMode && functions.size() > 1 && visitor->supportVqeMode()) {
auto kernelDecomposed = ObservedAnsatz::fromObservedComposites(functions);
// Always validate kernel decomposition in DEBUG
assert(kernelDecomposed.validate(functions));
visitor->setOptions(options);
// Initialize the visitor
visitor->initialize(buffer, getShotCountOption(options));
// Walk the IR tree, and visit each node
InstructionIterator it(std::dynamic_pointer_cast<CompositeInstruction>(
functions[0]->getInstruction(0)));
visitor->setKernelName(kernelDecomposed.getBase()->name());
// Walk the base IR tree, and visit each node
InstructionIterator it(kernelDecomposed.getBase());
while (it.hasNext()) {
auto nextInst = it.next();
if (nextInst->isEnabled() && !nextInst->isComposite()) {
......@@ -69,25 +67,21 @@ void TNQVM::execute(
}
}
// Clean way to remove the ansatz and just have measurements
auto ir = xacc::getIRProvider("quantum")->createIR();
for (auto &f : functions)
f->getInstruction(0)->disable();
// Now we have a wavefunction that represents
// execution of the ansatz. Make measurements
for (int i = 0; i < functions.size(); i++) {
// Now we have a wavefunction that represents execution of the ansatz.
// Run the observable sub-circuits (change of basis + measurements)
auto obsCircuits = kernelDecomposed.getObservedSubCircuits();
for (int i = 0; i < obsCircuits.size(); ++i) {
auto tmpBuffer = std::make_shared<xacc::AcceleratorBuffer>(
functions[i]->name(), buffer->size());
double e = visitor->getExpectationValueZ(functions[i]);
obsCircuits[i]->name(), buffer->size());
double e = visitor->getExpectationValueZ(obsCircuits[i]);
tmpBuffer->addExtraInfo("exp-val-z", e);
buffer->appendChild(functions[i]->name(), tmpBuffer);
buffer->appendChild(obsCircuits[i]->name(), tmpBuffer);
}
for (auto &f : functions)
f->getInstruction(0)->enable();
} else {
// Finalize the visitor
visitor->finalize();
}
// Normal execution mode
else {
for (auto f : functions) {
auto tmpBuffer =
std::make_shared<xacc::AcceleratorBuffer>(f->name(), buffer->size());
......@@ -109,15 +103,15 @@ void TNQVM::execute(std::shared_ptr<xacc::AcceleratorBuffer> buffer,
visitor->initialize(buffer, getShotCountOption(options));
visitor->setKernelName(kernel->name());
// If this is an Exatn-MPS visitor, transform the kernel to nearest-neighbor
// Note: currently, we don't support MPS aggregated blocks (multiple qubit MPS tensors in one block).
// Hence, the circuit must always be transformed into *nearest* neighbor only (distance = 1 for two-qubit gates).
if (visitor->name() == "exatn-mps")
{
// Note: currently, we don't support MPS aggregated blocks (multiple qubit MPS
// tensors in one block). Hence, the circuit must always be transformed into
// *nearest* neighbor only (distance = 1 for two-qubit gates).
if (visitor->name() == "exatn-mps") {
auto opt = xacc::getService<xacc::IRTransformation>("lnn-transform");
opt->apply(kernel, nullptr, { std::make_pair("max-distance", 1)});
// std::cout << "After LNN transform: \n" << kernel->toString() << "\n";
opt->apply(kernel, nullptr, {std::make_pair("max-distance", 1)});
// std::cout << "After LNN transform: \n" << kernel->toString() << "\n";
}
// Walk the IR tree, and visit each node
InstructionIterator it(kernel);
while (it.hasNext()) {
......
......@@ -5,4 +5,6 @@ target_link_libraries(TNQVMTester xacc::xacc)
if (EXATN_DIR)
add_xacc_test(ExatnVisitor)
target_link_libraries(ExatnVisitorTester tnqvm)
add_xacc_test(VQEMode)
target_link_libraries(VQEModeTester xacc::xacc xacc::pauli xacc::quantum_gate)
endif()
#include <memory>
#include <gtest/gtest.h>
#include "xacc.hpp"
#include "xacc_service.hpp"
#include "Optimizer.hpp"
#include "xacc_observable.hpp"
#include "Algorithm.hpp"
using namespace xacc;
TEST(VQEModeTester, checkH2)
{
auto accelerator = xacc::getAccelerator("tnqvm", { std::make_pair("tnqvm-visitor", "exatn") });
// Create the N=2 deuteron Hamiltonian
auto H_N_2 = xacc::quantum::getObservable(
"pauli", std::string("5.907 - 2.1433 X0X1 "
"- 2.1433 Y0Y1"
"+ .21829 Z0 - 6.125 Z1"));
auto optimizer = xacc::getOptimizer("nlopt");
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");
// Get the VQE Algorithm and initialize it
auto vqe = xacc::getAlgorithm("vqe");
vqe->initialize({std::make_pair("ansatz", ansatz),
std::make_pair("observable", H_N_2),
std::make_pair("accelerator", accelerator),
std::make_pair("optimizer", optimizer)});
// Allocate some qubits and execute
auto buffer = xacc::qalloc(2);
vqe->execute(buffer);
std::cout << "Energy = " << (*buffer)["opt-val"].as<double>() << "\n";
// Expected result: -1.74886
EXPECT_NEAR((*buffer)["opt-val"].as<double>(), -1.74886, 1e-4);
}
TEST(VQEModeTester, checkH3)
{
auto accelerator = xacc::getAccelerator("tnqvm", { std::make_pair("tnqvm-visitor", "exatn") });
// Create the N=3 deuteron Hamiltonian
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"));
auto optimizer = xacc::getOptimizer("nlopt");
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");
// Get the VQE Algorithm and initialize it
auto vqe = xacc::getAlgorithm("vqe");
vqe->initialize({std::make_pair("ansatz", ansatz),
std::make_pair("observable", H_N_3),
std::make_pair("accelerator", accelerator),
std::make_pair("optimizer", optimizer)});
// Allocate some qubits and execute
auto buffer = xacc::qalloc(3);
vqe->execute(buffer);
std::cout << "Energy = " << (*buffer)["opt-val"].as<double>() << "\n";
// Expected result: -2.04482
EXPECT_NEAR((*buffer)["opt-val"].as<double>(), -2.04482, 1e-4);
}
TEST(VQEModeTester, checkUcc)
{
const std::string rucc = R"rucc(__qpu__ void f(qbit q, double t0) {
X(q[0]);
X(q[1]);
Rx(q[0],1.5707);
H(q[1]);
H(q[2]);
H(q[3]);
CNOT(q[0],q[1]);
CNOT(q[1],q[2]);
CNOT(q[2],q[3]);
Rz(q[3], t0);
CNOT(q[2],q[3]);
CNOT(q[1],q[2]);
CNOT(q[0],q[1]);
Rx(q[0],-1.5707);
H(q[1]);
H(q[2]);
H(q[3]);
})rucc";
auto acc = xacc::getAccelerator("tnqvm", { std::make_pair("tnqvm-visitor", "exatn") });
auto buffer = xacc::qalloc(4);
auto compiler = xacc::getCompiler("xasm");
auto ir = compiler->compile(rucc, nullptr);
auto ruccsd = ir->getComposite("f");
auto optimizer = xacc::getOptimizer("nlopt");
auto observable = xacc::quantum::getObservable("pauli", std::string(
"(0.174073,0) Z2 Z3 + (0.1202,0) Z1 Z3 + (0.165607,0) Z1 Z2 + "
"(0.165607,0) Z0 Z3 + (0.1202,0) Z0 Z2 + (-0.0454063,0) Y0 Y1 X2 X3 + "
"(-0.220041,0) Z3 + (-0.106477,0) + (0.17028,0) Z0 + (-0.220041,0) Z2 "
"+ (0.17028,0) Z1 + (-0.0454063,0) X0 X1 Y2 Y3 + (0.0454063,0) X0 Y1 "
"Y2 X3 + (0.168336,0) Z0 Z1 + (0.0454063,0) Y0 X1 X2 Y3"));
auto vqe = xacc::getService<Algorithm>("vqe");
EXPECT_TRUE(vqe->initialize({{"ansatz", ruccsd},
{"accelerator", acc},
{"observable", observable},
{"optimizer", optimizer}}));
vqe->execute(buffer);
std::cout << "Energy = " << (*buffer)["opt-val"].as<double>() << "\n";
EXPECT_NEAR(-1.13717, (*buffer)["opt-val"].as<double>(), 1e-4);
}
int main(int argc, char **argv)
{
xacc::set_verbose(true);
xacc::Initialize();
::testing::InitGoogleTest(&argc, argv);
auto ret = RUN_ALL_TESTS();
xacc::Finalize();
return ret;
}
......@@ -68,6 +68,9 @@ public:
virtual void finalize() = 0;
void setOptions(const HeterogeneousMap& in_options) { options = in_options; }
virtual void setKernelName(const std::string& in_kernelName) {}
// Does this visitor implementation support VQE mode execution?
// i.e. ability to cache the state vector after simulating the ansatz.
virtual bool supportVqeMode() const { return false; }
protected:
std::shared_ptr<AcceleratorBuffer> buffer;
HeterogeneousMap options;
......
......@@ -90,6 +90,30 @@ bool checkStateVectorNorm(
return (std::abs(norm - 1.0) < 1e-12);
}
double calcExpValueZ(const std::vector<int>& in_bits, const std::vector<std::complex<double>>& in_stateVec)
{
TNQVM_TELEMETRY_ZONE("calcExpValueZ", __FILE__, __LINE__);
const auto hasEvenParity = [](uint64_t x, const std::vector<int>& in_qubitIndices) -> bool {
size_t count = 0;
for (const auto& bitIdx : in_qubitIndices)
{
if (x & (1ULL << bitIdx))
{
count++;
}
}
return (count % 2) == 0;
};
double result = 0.0;
for(uint64_t i = 0; i < in_stateVec.size(); ++i)
{
result += (hasEvenParity(i, in_bits) ? 1.0 : -1.0) * std::norm(in_stateVec[i]);
}
return result;
}
} // namespace
namespace tnqvm {
......@@ -781,28 +805,6 @@ void ExatnVisitor::finalize() {
{
if (!m_measureQbIdx.empty())
{
const auto calcExpValueZ = [](const std::vector<int>& in_bits, const std::vector<std::complex<double>>& in_stateVec) {
const auto hasEvenParity = [](uint64_t x, const std::vector<int>& in_qubitIndices) -> bool {
size_t count = 0;
for (const auto& bitIdx : in_qubitIndices)
{
if (x & (1ULL << bitIdx))
{
count++;
}
}
return (count % 2) == 0;
};
double result = 0.0;
for(uint64_t i = 0; i < in_stateVec.size(); ++i)
{
result += (hasEvenParity(i, in_bits) ? 1.0 : -1.0) * std::norm(in_stateVec[i]);
}
return result;
};
const auto tensorData = retrieveStateVector();
const double exp_val_z = calcExpValueZ(m_measureQbIdx, tensorData);
m_buffer->addExtraInfo("exp-val-z", exp_val_z);
......@@ -1423,25 +1425,82 @@ const double ExatnVisitor::getExpectationValueZ(
"getExpectationValueZ()!");
return 0.0;
}
// The new qubit register tensor name will have name "RESET_"
const std::string resetTensorName = "RESET_";
if (!m_hasEvaluated)
{
{
TNQVM_TELEMETRY_ZONE("exatn::evaluateSync", __FILE__, __LINE__);
const bool evaluated = exatn::evaluateSync(m_tensorNetwork);
assert(evaluated);
// Synchronize:
exatn::sync();
m_cacheStateVec = retrieveStateVector();
}
// State vector after the base ansatz
assert(m_cacheStateVec.size() == (1ULL << m_buffer->size()));
// The qubit register tensor shape is {2, 2, 2, ...}, 1 leg for each qubit
std::vector<int> qubitRegResetTensorShape(m_buffer->size(), 2);
const bool created = exatn::createTensor(resetTensorName, exatn::TensorElementType::COMPLEX64, qubitRegResetTensorShape);
assert(created);
// Initialize the tensor body with the state vector from the previous
// evaluation.
const bool initialized = exatn::initTensorData(resetTensorName, m_cacheStateVec);
assert(initialized);
for (auto iter = m_qubitRegTensor.cbegin(); iter != m_qubitRegTensor.cend(); ++iter)
{
const auto& tensorName = iter->second.getTensor()->getName();
// Not a root tensor
if (!tensorName.empty() && tensorName[0] != '_')
{
const bool destroyed = exatn::destroyTensorSync(tensorName);
assert(destroyed);
}
}
}
// Walk the circuit and visit all gates
// Create a new tensor network
m_tensorNetwork = TensorNetwork(in_function->name());
// Reset counter
m_tensorIdCounter = 1;
m_measureQbIdx.clear();
// Use the root tensor from previous evaluation as the initial tensor
m_tensorNetwork.appendTensor(m_tensorIdCounter, exatn::getTensor(resetTensorName), std::vector<std::pair<unsigned int, unsigned int>>{});
// Walk the remaining circuit and visit all gates
InstructionIterator it(in_function);
while (it.hasNext()) {
m_hasEvaluated = false;
size_t nbBasisChangeInsts = 0;
while (it.hasNext())
{
auto nextInst = it.next();
if (nextInst->isEnabled()) {
nextInst->accept(this);
if (nextInst->isEnabled() && !nextInst->isComposite())
{
if (nextInst->name() != "Measure")
{
nextInst->accept(this);
nbBasisChangeInsts++;
}
else
{
m_measureQbIdx.emplace_back(nextInst->bits()[0]);
}
}
}
if (!m_hasEvaluated) {
// No measurement was specified in the circuit.
xacc::warning("Expectation Value Z cannot be evaluated because there is no "
"measurement.");
return 0.0;
assert(!m_measureQbIdx.empty());
// If there are basis change instructions:
// i.e. not Z basis
if (nbBasisChangeInsts > 0)
{
TNQVM_TELEMETRY_ZONE("exatn::evaluateSync", __FILE__, __LINE__);
const bool evaluated = exatn::evaluateSync(m_tensorNetwork);
assert(evaluated);
}
const auto expectation = (*m_buffer)["exp-val-z"].as<double>();
return expectation;
m_hasEvaluated = true;
const double exp_val_z = (nbBasisChangeInsts > 0) ? calcExpValueZ(m_measureQbIdx, retrieveStateVector()) : calcExpValueZ(m_measureQbIdx, m_cacheStateVec);
m_measureQbIdx.clear();
return exp_val_z;
}
std::vector<uint8_t> ExatnVisitor::generateMeasureSample(const TensorNetwork& in_tensorNetwork, const std::vector<int>& in_qubitIdx)
......
......@@ -223,7 +223,7 @@ namespace tnqvm {
virtual void visit(fSim& in_fsimGate) override;
// others
virtual void visit(Measure& in_MeasureGate) override;
virtual bool supportVqeMode() const override { return true; }
virtual const double getExpectationValueZ(std::shared_ptr<CompositeInstruction> in_function) override;
void subscribe(IExatnListener* listener) { m_listeners.emplace_back(listener); }
......@@ -315,6 +315,7 @@ namespace tnqvm {
// Tensor network of the qubit register (to close the tensor network for expectation calculation)
TensorNetwork m_qubitRegTensor;
std::string m_kernelName;
std::vector<std::complex<double>> m_cacheStateVec;
// Make the debug logger friend, e.g. retrieve internal states for logging purposes.
friend class ExatnDebugLogger;
};
......