Commit 1e8874a2 authored by Thien Nguyen's avatar Thien Nguyen
Browse files

Fixed https://github.com/eclipse/xacc/issues/509



- Gradient strategies to filter out non-observing circuits associated with identity term.

- For two-sided gradients (central and parameter shift), the identity term has no effect. For single-sided ones (forward and backward), add the identity coefficient in the shifted enery value.

- Fixed an oversight in setting optional 'step' param. It was only set in the ctor, but not reset during initialization. Hence, it may carry the value set by a previous run.

- Added a set of unit test cases covering 'shots' mode

Signed-off-by: default avatarThien Nguyen <thien.md.nguyen@gmail.com>
parent 46d11bc1
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "xacc_service.hpp" #include "xacc_service.hpp"
#include "AlgorithmGradientStrategy.hpp" #include "AlgorithmGradientStrategy.hpp"
#include <iomanip> #include <iomanip>
#include "InstructionIterator.hpp"
using namespace xacc; using namespace xacc;
...@@ -52,6 +53,8 @@ public: ...@@ -52,6 +53,8 @@ public:
obs = parameters.get<std::shared_ptr<Observable>>("observable"); obs = parameters.get<std::shared_ptr<Observable>>("observable");
// Default step size
step = 1.0e-7;
// Change step size if need be // Change step size if need be
if (parameters.keyExists<double>("step")) { if (parameters.keyExists<double>("step")) {
step = parameters.get<double>("step"); step = parameters.get<double>("step");
...@@ -70,7 +73,18 @@ public: ...@@ -70,7 +73,18 @@ public:
std::vector<std::shared_ptr<CompositeInstruction>> std::vector<std::shared_ptr<CompositeInstruction>>
getGradientExecutions(std::shared_ptr<CompositeInstruction> circuit, getGradientExecutions(std::shared_ptr<CompositeInstruction> circuit,
const std::vector<double> &x) override { const std::vector<double> &x) override {
// Check if the composite contains measure gates
const auto containMeasureGates =
[](std::shared_ptr<CompositeInstruction> f) -> bool {
InstructionIterator it(f);
while (it.hasNext()) {
auto nextInst = it.next();
if (nextInst->name() == "Measure") {
return true;
}
}
return false;
};
// std::stringstream ss; // std::stringstream ss;
// ss << std::setprecision(5) << "Input parameters: "; // ss << std::setprecision(5) << "Input parameters: ";
// for (auto param : x) { // for (auto param : x) {
...@@ -90,8 +104,10 @@ public: ...@@ -90,8 +104,10 @@ public:
auto evaled_base = kernel_evaluator(tmpX); auto evaled_base = kernel_evaluator(tmpX);
kernels = obs->observe(evaled_base); kernels = obs->observe(evaled_base);
for (auto &f : kernels) { for (auto &f : kernels) {
coefficients.push_back(std::real(f->getCoefficient())); if (containMeasureGates(f)) {
gradientInstructions.push_back(f); coefficients.push_back(std::real(f->getCoefficient()));
gradientInstructions.push_back(f);
}
} }
} else { } else {
kernels = obs->observe(circuit); kernels = obs->observe(circuit);
...@@ -99,13 +115,18 @@ public: ...@@ -99,13 +115,18 @@ public:
// loop over circuit instructions // loop over circuit instructions
// and gather coefficients/instructions // and gather coefficients/instructions
for (auto &f : kernels) { for (auto &f : kernels) {
auto evaled = f->operator()(tmpX); if (containMeasureGates(f)) {
coefficients.push_back(std::real(f->getCoefficient())); auto evaled = f->operator()(tmpX);
gradientInstructions.push_back(evaled); coefficients.push_back(std::real(f->getCoefficient()));
gradientInstructions.push_back(evaled);
}
} }
} }
nInstructionsElement.push_back(kernels.size()); const auto numberGradKernels = std::count_if(
kernels.begin(), kernels.end(),
[&containMeasureGates](auto &f) { return containMeasureGates(f); });
nInstructionsElement.push_back(numberGradKernels);
} }
return gradientInstructions; return gradientInstructions;
...@@ -119,8 +140,9 @@ public: ...@@ -119,8 +140,9 @@ public:
int shift = 0; int shift = 0;
// loop over the remaining number of entries in the gradient vector // loop over the remaining number of entries in the gradient vector
for (int gradTerm = 0; gradTerm < dx.size(); gradTerm++) { for (int gradTerm = 0; gradTerm < dx.size(); gradTerm++) {
auto identityTerm = obs->getIdentitySubTerm();
double gradElement = 0.0; double gradElement =
identityTerm ? identityTerm->coefficient().real() : 0.0;
// loop over instructions for a given term, compute <+> and <-> // loop over instructions for a given term, compute <+> and <->
for (int instElement = 0; instElement < nInstructionsElement[gradTerm]; for (int instElement = 0; instElement < nInstructionsElement[gradTerm];
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "xacc_service.hpp" #include "xacc_service.hpp"
#include "AlgorithmGradientStrategy.hpp" #include "AlgorithmGradientStrategy.hpp"
#include <iomanip> #include <iomanip>
#include "InstructionIterator.hpp"
using namespace xacc; using namespace xacc;
...@@ -52,6 +53,8 @@ public: ...@@ -52,6 +53,8 @@ public:
obs = parameters.get<std::shared_ptr<Observable>>("observable"); obs = parameters.get<std::shared_ptr<Observable>>("observable");
// Default value
step = 1.0e-7;
// Change step size if need be // Change step size if need be
if (parameters.keyExists<double>("step")) { if (parameters.keyExists<double>("step")) {
step = parameters.get<double>("step"); step = parameters.get<double>("step");
...@@ -72,7 +75,18 @@ public: ...@@ -72,7 +75,18 @@ public:
std::vector<std::shared_ptr<CompositeInstruction>> std::vector<std::shared_ptr<CompositeInstruction>>
getGradientExecutions(std::shared_ptr<CompositeInstruction> circuit, getGradientExecutions(std::shared_ptr<CompositeInstruction> circuit,
const std::vector<double> &x) override { const std::vector<double> &x) override {
// Check if the composite contains measure gates
const auto containMeasureGates =
[](std::shared_ptr<CompositeInstruction> f) -> bool {
InstructionIterator it(f);
while (it.hasNext()) {
auto nextInst = it.next();
if (nextInst->name() == "Measure") {
return true;
}
}
return false;
};
std::vector<std::shared_ptr<CompositeInstruction>> gradientInstructions; std::vector<std::shared_ptr<CompositeInstruction>> gradientInstructions;
for (int op = 0; op < x.size(); op++) { // loop over operators for (int op = 0; op < x.size(); op++) { // loop over operators
for (double sign : {1.0, -1.0}) { // change sign for (double sign : {1.0, -1.0}) { // change sign
...@@ -86,8 +100,10 @@ public: ...@@ -86,8 +100,10 @@ public:
auto evaled_base = kernel_evaluator(tmpX); auto evaled_base = kernel_evaluator(tmpX);
kernels = obs->observe(evaled_base); kernels = obs->observe(evaled_base);
for (auto &f : kernels) { for (auto &f : kernels) {
coefficients.push_back(std::real(f->getCoefficient())); if (containMeasureGates(f)) {
gradientInstructions.push_back(f); coefficients.push_back(std::real(f->getCoefficient()));
gradientInstructions.push_back(f);
}
} }
} else { } else {
kernels = obs->observe(circuit); kernels = obs->observe(circuit);
...@@ -95,15 +111,21 @@ public: ...@@ -95,15 +111,21 @@ public:
// loop over circuit instructions // loop over circuit instructions
// and gather coefficients/instructions // and gather coefficients/instructions
for (auto &f : kernels) { for (auto &f : kernels) {
auto evaled = f->operator()(tmpX); if (containMeasureGates(f)) {
coefficients.push_back(std::real(f->getCoefficient())); auto evaled = f->operator()(tmpX);
gradientInstructions.push_back(evaled); coefficients.push_back(std::real(f->getCoefficient()));
gradientInstructions.push_back(evaled);
}
} }
} }
// the number of instructions for a given element of x is the same // the number of instructions for a given element of x is the same
// regardless of the parameter sign, so we need only one of this // regardless of the parameter sign, so we need only one of this
if (sign == 1.0) { if (sign == 1.0) {
nInstructionsElement.push_back(kernels.size()); const auto numberGradKernels = std::count_if(
kernels.begin(), kernels.end(), [&containMeasureGates](auto &f) {
return containMeasureGates(f);
});
nInstructionsElement.push_back(numberGradKernels);
} }
} }
} }
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "xacc_service.hpp" #include "xacc_service.hpp"
#include "AlgorithmGradientStrategy.hpp" #include "AlgorithmGradientStrategy.hpp"
#include <iomanip> #include <iomanip>
#include "InstructionIterator.hpp"
using namespace xacc; using namespace xacc;
...@@ -52,6 +53,8 @@ public: ...@@ -52,6 +53,8 @@ public:
obs = parameters.get<std::shared_ptr<Observable>>("observable"); obs = parameters.get<std::shared_ptr<Observable>>("observable");
// Default step size
step = 1.0e-7;
// Change step size if need be // Change step size if need be
if (parameters.keyExists<double>("step")) { if (parameters.keyExists<double>("step")) {
step = parameters.get<double>("step"); step = parameters.get<double>("step");
...@@ -71,6 +74,18 @@ public: ...@@ -71,6 +74,18 @@ public:
getGradientExecutions(std::shared_ptr<CompositeInstruction> circuit, getGradientExecutions(std::shared_ptr<CompositeInstruction> circuit,
const std::vector<double> &x) override { const std::vector<double> &x) override {
// Check if the composite contains measure gates
const auto containMeasureGates =
[](std::shared_ptr<CompositeInstruction> f) -> bool {
InstructionIterator it(f);
while (it.hasNext()) {
auto nextInst = it.next();
if (nextInst->name() == "Measure") {
return true;
}
}
return false;
};
// std::stringstream ss; // std::stringstream ss;
// ss << std::setprecision(5) << "Input parameters: "; // ss << std::setprecision(5) << "Input parameters: ";
// for (auto param : x) { // for (auto param : x) {
...@@ -92,8 +107,10 @@ public: ...@@ -92,8 +107,10 @@ public:
auto evaled_base = kernel_evaluator(tmpX); auto evaled_base = kernel_evaluator(tmpX);
kernels = obs->observe(evaled_base); kernels = obs->observe(evaled_base);
for (auto &f : kernels) { for (auto &f : kernels) {
coefficients.push_back(std::real(f->getCoefficient())); if (containMeasureGates(f)) {
gradientInstructions.push_back(f); coefficients.push_back(std::real(f->getCoefficient()));
gradientInstructions.push_back(f);
}
} }
} else { } else {
kernels = obs->observe(circuit); kernels = obs->observe(circuit);
...@@ -101,13 +118,18 @@ public: ...@@ -101,13 +118,18 @@ public:
// loop over circuit instructions // loop over circuit instructions
// and gather coefficients/instructions // and gather coefficients/instructions
for (auto &f : kernels) { for (auto &f : kernels) {
auto evaled = f->operator()(tmpX); if (containMeasureGates(f)) {
coefficients.push_back(std::real(f->getCoefficient())); auto evaled = f->operator()(tmpX);
gradientInstructions.push_back(evaled); coefficients.push_back(std::real(f->getCoefficient()));
gradientInstructions.push_back(evaled);
}
} }
} }
nInstructionsElement.push_back(kernels.size()); const auto numberGradKernels = std::count_if(
kernels.begin(), kernels.end(),
[&containMeasureGates](auto &f) { return containMeasureGates(f); });
nInstructionsElement.push_back(numberGradKernels);
} }
return gradientInstructions; return gradientInstructions;
...@@ -117,12 +139,13 @@ public: ...@@ -117,12 +139,13 @@ public:
void void
compute(std::vector<double> &dx, compute(std::vector<double> &dx,
std::vector<std::shared_ptr<AcceleratorBuffer>> results) override { std::vector<std::shared_ptr<AcceleratorBuffer>> results) override {
int shift = 0; int shift = 0;
// loop over the remaining number of entries in the gradient vector // loop over the remaining number of entries in the gradient vector
for (int gradTerm = 0; gradTerm < dx.size(); gradTerm++) { for (int gradTerm = 0; gradTerm < dx.size(); gradTerm++) {
double gradElement = 0.0; auto identityTerm = obs->getIdentitySubTerm();
double gradElement =
identityTerm ? identityTerm->coefficient().real() : 0.0;
// loop over instructions for a given term, compute <+> and <-> // loop over instructions for a given term, compute <+> and <->
for (int instElement = 0; instElement < nInstructionsElement[gradTerm]; for (int instElement = 0; instElement < nInstructionsElement[gradTerm];
...@@ -151,10 +174,7 @@ public: ...@@ -151,10 +174,7 @@ public:
return; return;
} }
const std::string name() const override { return "forward"; }
const std::string name() const override {
return "forward";
}
const std::string description() const override { return ""; } const std::string description() const override { return ""; }
}; };
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#define XACC_PARAMETER_SHIFT_GRADIENT_HPP_ #define XACC_PARAMETER_SHIFT_GRADIENT_HPP_
#include "CompositeInstruction.hpp" #include "CompositeInstruction.hpp"
#include "InstructionIterator.hpp"
#include "xacc.hpp" #include "xacc.hpp"
#include "xacc_service.hpp" #include "xacc_service.hpp"
#include "AlgorithmGradientStrategy.hpp" #include "AlgorithmGradientStrategy.hpp"
...@@ -48,7 +49,9 @@ public: ...@@ -48,7 +49,9 @@ public:
parameters.get<std::function<std::shared_ptr<CompositeInstruction>( parameters.get<std::function<std::shared_ptr<CompositeInstruction>(
std::vector<double>)>>("kernel-evaluator"); std::vector<double>)>>("kernel-evaluator");
} }
// Default shiftScalar (this is not clonable, hence need to be
// reinitialized)
shiftScalar = 0.25;
if (parameters.keyExists<double>("shift-scalar")) { if (parameters.keyExists<double>("shift-scalar")) {
shiftScalar = parameters.get<double>("shift-scalar"); shiftScalar = parameters.get<double>("shift-scalar");
} }
...@@ -63,9 +66,23 @@ public: ...@@ -63,9 +66,23 @@ public:
std::vector<std::shared_ptr<CompositeInstruction>> std::vector<std::shared_ptr<CompositeInstruction>>
getGradientExecutions(std::shared_ptr<CompositeInstruction> circuit, getGradientExecutions(std::shared_ptr<CompositeInstruction> circuit,
const std::vector<double> &x) override { const std::vector<double> &x) override {
// Check if the composite contains measure gates
const auto containMeasureGates =
[](std::shared_ptr<CompositeInstruction> f) -> bool {
InstructionIterator it(f);
while (it.hasNext()) {
auto nextInst = it.next();
if (nextInst->name() == "Measure") {
return true;
}
}
return false;
};
std::vector<std::shared_ptr<CompositeInstruction>> gradientInstructions; std::vector<std::shared_ptr<CompositeInstruction>> gradientInstructions;
// Note: for the purpose of parameter-shift gradient calculation, the
// identity term has no effect, i.e., its contributions to the plus and
// minus sides will cancel out.
// loop over parameters // loop over parameters
for (int param = 0; param < x.size(); param++) { for (int param = 0; param < x.size(); param++) {
// parameter shift sign // parameter shift sign
...@@ -81,8 +98,10 @@ public: ...@@ -81,8 +98,10 @@ public:
auto evaled_base = kernel_evaluator(tmpX); auto evaled_base = kernel_evaluator(tmpX);
kernels = obs->observe(evaled_base); kernels = obs->observe(evaled_base);
for (auto &f : kernels) { for (auto &f : kernels) {
coefficients.push_back(std::real(f->getCoefficient())); if (containMeasureGates(f)) {
gradientInstructions.push_back(f); coefficients.push_back(std::real(f->getCoefficient()));
gradientInstructions.push_back(f);
}
} }
} else { } else {
kernels = obs->observe(circuit); kernels = obs->observe(circuit);
...@@ -90,16 +109,22 @@ public: ...@@ -90,16 +109,22 @@ public:
// loop over circuit instructions // loop over circuit instructions
// and gather coefficients/instructions // and gather coefficients/instructions
for (auto &f : kernels) { for (auto &f : kernels) {
auto evaled = f->operator()(tmpX); if (containMeasureGates(f)) {
coefficients.push_back(std::real(f->getCoefficient())); auto evaled = f->operator()(tmpX);
gradientInstructions.push_back(evaled); coefficients.push_back(std::real(f->getCoefficient()));
gradientInstructions.push_back(evaled);
}
} }
} }
// the number of instructions for a given element of x is the same // the number of instructions for a given element of x is the same
// regardless of the parameter sign, so we need only one of this // regardless of the parameter sign, so we need only one of this.
if (sign == 1.0) { if (sign == 1.0) {
nInstructionsElement.push_back(kernels.size()); const auto numberGradKernels = std::count_if(
kernels.begin(), kernels.end(), [&containMeasureGates](auto &f) {
return containMeasureGates(f);
});
nInstructionsElement.push_back(numberGradKernels);
} }
} }
} }
...@@ -121,7 +146,6 @@ public: ...@@ -121,7 +146,6 @@ public:
// loop over instructions for a given term, compute <+> and <-> // loop over instructions for a given term, compute <+> and <->
for (int instElement = 0; instElement < nInstructionsElement[gradTerm]; for (int instElement = 0; instElement < nInstructionsElement[gradTerm];
instElement++) { instElement++) {
auto plus_expval = auto plus_expval =
std::real(results[instElement + shift]->getExpectationValueZ()); std::real(results[instElement + shift]->getExpectationValueZ());
auto minus_expval = std::real( auto minus_expval = std::real(
......
...@@ -200,6 +200,151 @@ TEST(GradientStrategiesTester, checkYanPSproblem) { ...@@ -200,6 +200,151 @@ TEST(GradientStrategiesTester, checkYanPSproblem) {
EXPECT_NEAR((*q)["opt-val"].as<double>(), -1.0, 1e-4); EXPECT_NEAR((*q)["opt-val"].as<double>(), -1.0, 1e-4);
} }
// Check running shots:
TEST(GradientStrategiesTester, checkParameterShiftShots) {
auto accelerator = xacc::getAccelerator("aer", {{"shots", 65536}});
auto buffer = xacc::qalloc(1);
std::shared_ptr<Observable> observable =
std::make_shared<xacc::quantum::PauliOperator>();
observable->fromString("X0");
auto provider = xacc::getIRProvider("quantum");
auto ansatz = provider->createComposite("testCircuit");
ansatz->addVariable("x0");
ansatz->addInstruction(provider->createInstruction(
"Ry", std::vector<std::size_t>{0}, {InstructionParameter("x0")}));
auto parameterShift =
xacc::getService<AlgorithmGradientStrategy>("parameter-shift");
parameterShift->initialize({std::make_pair("observable", observable)});
auto gradientInstructions =
parameterShift->getGradientExecutions(ansatz, {0.0});
accelerator->execute(buffer, gradientInstructions);
std::vector<double> dx(1);
parameterShift->compute(dx, buffer->getChildren());
EXPECT_NEAR(dx[0], std::sqrt(2), 0.1);
}
TEST(GradientStrategiesTester, checkCentralDifferenceShots) {
auto accelerator2 = xacc::getAccelerator("aer", {{"shots", 65536}});
// auto accelerator2 = xacc::getAccelerator("qpp");
auto buffer2 = xacc::qalloc(1);
std::shared_ptr<Observable> observable2 =
std::make_shared<xacc::quantum::PauliOperator>();
observable2->fromString("X0");
auto provider2 = xacc::getIRProvider("quantum");
auto ansatz2 = provider2->createComposite("testCircuit");
ansatz2->addVariable("x0");
ansatz2->addInstruction(provider2->createInstruction(
"Ry", std::vector<std::size_t>{0}, {InstructionParameter("x0")}));
auto centralDifference =
xacc::getService<AlgorithmGradientStrategy>("central");
centralDifference->initialize({{"observable", observable2}, {"step", 0.1}});
auto gradientInstructions =
centralDifference->getGradientExecutions(ansatz2, {0.0});
accelerator2->execute(buffer2, gradientInstructions);
std::vector<double> dx(1);
centralDifference->compute(dx, buffer2->getChildren());
EXPECT_NEAR(dx[0], 1.0, 0.1);
}
TEST(GradientStrategiesTester, checkForwardDifferenceShots) {
auto accelerator3 = xacc::getAccelerator("aer", {{"shots", 65536}});
auto buffer3 = xacc::qalloc(1);
std::shared_ptr<Observable> observable3 =
std::make_shared<xacc::quantum::PauliOperator>();
observable3->fromString("X0");
auto provider3 = xacc::getIRProvider("quantum");
auto ansatz3 = provider3->createComposite("testCircuit");
ansatz3->addVariable("x0");
ansatz3->addInstruction(provider3->createInstruction(
"Ry", std::vector<std::size_t>{0}, {InstructionParameter("x0")}));
auto forwardDifference =
xacc::getService<AlgorithmGradientStrategy>("forward");
forwardDifference->initialize({{"observable", observable3}, {"step", 0.1}});
auto gradientInstructions =
forwardDifference->getGradientExecutions(ansatz3, {0.0});
accelerator3->execute(buffer3, gradientInstructions);
std::vector<double> dx(1);
forwardDifference->compute(dx, buffer3->getChildren());
EXPECT_NEAR(dx[0], 1.0, 0.1);
}
TEST(GradientStrategiesTester, checkBackwardDifferenceShots) {
auto accelerator4 = xacc::getAccelerator("aer", {{"shots", 65536}});
auto buffer4 = xacc::qalloc(1);
std::shared_ptr<Observable> observable4 =
std::make_shared<xacc::quantum::PauliOperator>();
observable4->fromString("X0");
auto provider4 = xacc::getIRProvider("quantum");
auto ansatz4 = provider4->createComposite("testCircuit");
ansatz4->addVariable("x0");
ansatz4->addInstruction(provider4->createInstruction(