Unverified Commit f4a9e04a authored by Mccaskey, Alex's avatar Mccaskey, Alex Committed by GitHub
Browse files

Merge pull request #181 from tnguyen-ornl/tnguyen/pulse-optim-pybind

Work to enable external (i.e. Python) pulse optimization implementation
parents e0be6d19 0ec27e6d
Pipeline #95602 passed with stage
in 69 minutes and 17 seconds
......@@ -27,6 +27,11 @@ void bind_heterogeneous_map(py::module &m) {
const std::vector<double> &)) &
xacc::HeterogeneousMap::insert,
"")
.def("insert",
(void (xacc::HeterogeneousMap::*)(const std::string,
const std::vector<std::complex<double>> &)) &
xacc::HeterogeneousMap::insert,
"")
.def("insert",
(void (xacc::HeterogeneousMap::*)(
const std::string, const std::vector<std::pair<int, int>> &)) &
......@@ -56,6 +61,8 @@ void bind_heterogeneous_map(py::module &m) {
return m.getString(key);
} else if (m.keyExists<std::vector<double>>(key)) {
return m.get<std::vector<double>>(key);
} else if (m.keyExists<std::vector<std::complex<double>>>(key)) {
return m.get<std::vector<std::complex<double>>>(key);
} else if (m.keyExists<std::vector<int>>(key)) {
return m.get<std::vector<int>>(key);
} else if (m.keyExists<std::shared_ptr<Observable>>(key)) {
......@@ -76,6 +83,7 @@ void bind_heterogeneous_map(py::module &m) {
return m.keyExists<int>(key) || m.keyExists<double>(key) ||
m.keyExists<bool>(key) || m.stringExists(key) ||
m.keyExists<std::vector<double>>(key) ||
m.keyExists<std::vector<std::complex<double>>>(key) ||
m.keyExists<std::vector<int>>(key) ||
m.keyExists<std::vector<std::string>>(key) ||
m.keyExists<std::shared_ptr<Observable>>(key) ||
......@@ -87,7 +95,7 @@ void bind_heterogeneous_map(py::module &m) {
m, "PyHeterogeneousMap",
"The PyHeterogeneousMap provides a variant structure "
"to provide parameters to XACC HeterogeneousMaps."
"This type can be an int, double, string, List[int], List[double]"
"This type can be an int, double, string, List[int], List[double], List[complex]"
"List[string], Observable, Accelerator, Function, or Optimizer.")
.def(py::init<int>(), "Construct as an int.")
.def(py::init<bool>(), "Construct as a bool")
......@@ -96,6 +104,7 @@ void bind_heterogeneous_map(py::module &m) {
.def(py::init<std::vector<std::string>>(), "Construct as a List[string].")
.def(py::init<std::vector<int>>(), "Construct as a List[int].")
.def(py::init<std::vector<double>>(), "Construct as a List[double].")
.def(py::init<std::vector<std::complex<double>>>(), "Construct as a List[complex].")
.def(py::init<std::shared_ptr<xacc::Accelerator>>(),
"Construct as an Accelerator.")
.def(py::init<std::shared_ptr<xacc::CompositeInstruction>>(),
......
......@@ -55,7 +55,7 @@ template <> struct visit_helper<mpark::variant> {
// associated map to fake like it is a HeterogeneousMap
using PyHeterogeneousMapTypes =
xacc::Variant<bool, int, double, std::string, std::vector<std::string>,
std::vector<double>, std::vector<int>, std::complex<double>,
std::vector<double>, std::vector<int>, std::complex<double>, std::vector<std::complex<double>>,
std::shared_ptr<CompositeInstruction>,
std::shared_ptr<Instruction>, std::shared_ptr<Accelerator>,
std::shared_ptr<Observable>, std::shared_ptr<Optimizer>>;
......@@ -78,7 +78,7 @@ public:
class HeterogeneousMap2PyHeterogeneousMap
: public visitor_base<
bool, int, double, std::string, std::vector<std::string>,
std::vector<double>, std::vector<int>, std::complex<double>,
std::vector<double>, std::vector<int>, std::complex<double>, std::vector<std::complex<double>>,
std::shared_ptr<CompositeInstruction>, std::shared_ptr<Instruction>,
std::shared_ptr<Accelerator>, std::shared_ptr<Observable>,
std::shared_ptr<Optimizer>> {
......
......@@ -39,6 +39,11 @@ public:
OptResult optimize(OptFunction &function) override {
PYBIND11_OVERLOAD_PURE(OptResult, xacc::Optimizer, optimize, function);
}
OptResult optimize() override {
PYBIND11_OVERLOAD(OptResult, xacc::Optimizer, optimize);
}
void setOptions(const HeterogeneousMap &opts) override {
PYBIND11_OVERLOAD_PURE(void, xacc::Optimizer, setOptions, opts);
}
......
......@@ -478,11 +478,11 @@ namespace xacc {
// - Required: { "max-time" : double }: max control time horizon
//
// - Optional: { "optimizer" : string }: can be "ml-pack" or "default"
bool PulseOptimGOAT::initialize(const HeterogeneousMap& in_options)
void PulseOptimGOAT::setOptions(const HeterogeneousMap& in_options)
{
const auto fatalError = [](const std::string& in_fieldName){
xacc::error("Fatal: '" + in_fieldName + "' field is required.");
return false;
return;
};
// Handle GOAT optimal control
......@@ -566,43 +566,43 @@ bool PulseOptimGOAT::initialize(const HeterogeneousMap& in_options)
if (dimension < 1 || dimension > 10)
{
xacc::error("Invalid system dimension.");
return false;
return;
}
if (controlFuncs.size() != controlOps.size())
{
xacc::error("The number of control functions must match the number of time-dependent Hamiltonian terms.");
return false;
return;
}
if (controlFuncs.empty())
{
xacc::error("No control terms were specified.");
return false;
return;
}
if (initParams.size() != controlParams.size())
{
xacc::error("The number of initial values must match the number control parameters.");
return false;
return;
}
if (initParams.empty())
{
xacc::error("No control parameters were specified.");
return false;
return;
}
if (tMax < 0.0)
{
xacc::error("Invalid max time parameter.");
return false;
return;
}
if (!optimizer.empty() && optimizer != "default" && optimizer != "ml-pack")
{
xacc::error("Invalid optimizer.");
return false;
return;
}
std::unique_ptr<IGradientStepper> gradientOptimizer = [](const std::string& in_optimizerName) -> std::unique_ptr<IGradientStepper> {
......@@ -619,7 +619,6 @@ bool PulseOptimGOAT::initialize(const HeterogeneousMap& in_options)
m_hamiltonian->construct(dimension, H0, controlOps, controlFuncs, controlParams);
// All parameters have been validated: construct the GOAT pulse optimizer
m_goatOptimizer = std::make_unique<GOAT_PulseOptim>(targetUmat, m_hamiltonian->hamiltonian, m_hamiltonian->dHda, initParams, tMax, nullptr, std::move(gradientOptimizer));
return true;
}
OptResult PulseOptimGOAT::optimize()
......
......@@ -123,13 +123,15 @@ private:
namespace xacc {
// Public GOAT pulse optimization interface
class PulseOptimGOAT : public PulseOptim
class PulseOptimGOAT : public Optimizer
{
public:
const std::string name() const override { return "GOAT"; }
const std::string description() const override { return ""; }
bool initialize(const HeterogeneousMap& in_options) override;
void setOptions(const HeterogeneousMap& in_options) override;
OptResult optimize() override;
OptResult optimize(OptFunction& function) override { return optimize(); }
private:
std::unique_ptr<GOAT_PulseOptim> m_goatOptimizer;
std::unique_ptr<GoatHamiltonian> m_hamiltonian;
......
......@@ -156,7 +156,7 @@ namespace xacc {
// Note: initial-pulses and max-time are both optional *BUT* at least one of them
// must be provided.
// - Optional: { "eps": double}: gradient step size multiplier (default = 0.1 * (2 * pi) / max-time)
bool PulseOptimGRAPE::initialize(const HeterogeneousMap& in_options)
void PulseOptimGRAPE::setOptions(const HeterogeneousMap& in_options)
{
int dimension = 0;
if (in_options.keyExists<int>("dimension"))
......@@ -167,7 +167,7 @@ bool PulseOptimGRAPE::initialize(const HeterogeneousMap& in_options)
if (dimension < 1)
{
xacc::error("Invalid 'dimension' parameter.");
return false;
return;
}
m_nbIters = 1000;
......@@ -179,7 +179,7 @@ bool PulseOptimGRAPE::initialize(const HeterogeneousMap& in_options)
if (m_nbIters < 1)
{
xacc::error("Invalid 'max-iterations' parameter.");
return false;
return;
}
m_tol = 1e-10;
......@@ -211,7 +211,7 @@ bool PulseOptimGRAPE::initialize(const HeterogeneousMap& in_options)
else
{
xacc::error("Missing 'control-H' parameter.");
return false;
return;
}
std::vector<Eigen::MatrixXcd> H_ops;
for (const auto& opStr : controlOps)
......@@ -228,7 +228,7 @@ bool PulseOptimGRAPE::initialize(const HeterogeneousMap& in_options)
else
{
xacc::error("Missing 'dt' parameter.");
return false;
return;
}
int nbSamples = 0;
......@@ -245,7 +245,7 @@ bool PulseOptimGRAPE::initialize(const HeterogeneousMap& in_options)
if (initialPulses.size() != H_ops.size() || initialPulses.empty())
{
xacc::error("If provided, 'initial-pulses' parameter must contain all control pulses.");
return false;
return;
}
nbSamples = initialPulses[0].size();
......@@ -254,7 +254,7 @@ bool PulseOptimGRAPE::initialize(const HeterogeneousMap& in_options)
if (initPulse.empty() || initPulse.size() != nbSamples)
{
xacc::error("Invalid data array is provided in 'initial-pulses' parameter.");
return false;
return;
}
}
}
......@@ -287,7 +287,6 @@ bool PulseOptimGRAPE::initialize(const HeterogeneousMap& in_options)
}
m_optimizer = std::make_unique<GrapePulseOptim>(configs);
return true;
}
OptResult PulseOptimGRAPE::optimize()
......
......@@ -43,13 +43,14 @@ private:
namespace xacc {
// Public GRAPE pulse optimization interface
class PulseOptimGRAPE : public PulseOptim
class PulseOptimGRAPE : public Optimizer
{
public:
const std::string name() const override { return "GRAPE"; }
const std::string description() const override { return ""; }
bool initialize(const HeterogeneousMap& in_options) override;
void setOptions(const HeterogeneousMap& in_options) override;
OptResult optimize() override;
OptResult optimize(OptFunction& function) override { return optimize(); }
private:
std::unique_ptr<GrapePulseOptim> m_optimizer;
int m_nbIters;
......
......@@ -7,7 +7,7 @@ namespace xacc {
// Preliminary specs of the Optimal Control options:
// ***REQUIRE*** { "method" : string}
// The control method that we want to use.
// Available options: "GOAT"
// Available options: "GOAT", "GRAPE", etc.
// =============================================
void ControlOptimizer::setOptions(const HeterogeneousMap& in_options)
{
......@@ -18,18 +18,12 @@ void ControlOptimizer::setOptions(const HeterogeneousMap& in_options)
}
// This will throw if the method is not supported.
// Currently, we only have "GOAT" implemented
m_pulseOptim = xacc::getService<PulseOptim>(in_options.getString("method"));
m_initialized = m_pulseOptim->initialize(in_options);
m_pulseOptim = xacc::getOptimizer(in_options.getString("method"));
m_pulseOptim->setOptions(in_options);
}
OptResult ControlOptimizer::optimize()
{
if (!m_initialized)
{
xacc::error("Fatal: Optimal Control Module must be initialized before use!");
}
return m_pulseOptim->optimize();
}
......
......@@ -4,14 +4,6 @@
#include <Eigen/Dense>
namespace xacc {
// Pulse Optimization service interface
class PulseOptim: public Identifiable
{
public:
virtual bool initialize(const HeterogeneousMap& in_options) = 0;
virtual OptResult optimize() = 0;
};
class ControlOptimizer : public Optimizer
{
public:
......@@ -21,10 +13,7 @@ public:
const std::string name() const override { return "quantum-control"; }
const std::string description() const override { return ""; }
private:
// Unlike other optimizer, this optimal control service requires
// proper set-up (e.g. method to use, optimal control problem description)
bool m_initialized = false;
std::shared_ptr<PulseOptim> m_pulseOptim;
std::shared_ptr<Optimizer> m_pulseOptim;
};
// Util to calculate the unitary matrix from string
......
......@@ -32,9 +32,9 @@ public:
context.RegisterService<xacc::Optimizer>(std::make_shared<xacc::ControlOptimizer>());
context.RegisterService<xacc::UnitaryMatrixUtil>(std::make_shared<xacc::PauliUnitaryMatrixUtil>());
// Register the GOAT pulse optimization
context.RegisterService<xacc::PulseOptim>(std::make_shared<xacc::PulseOptimGOAT>());
context.RegisterService<xacc::Optimizer>(std::make_shared<xacc::PulseOptimGOAT>());
// Register the GRAPE pulse optimization
context.RegisterService<xacc::PulseOptim>(std::make_shared<xacc::PulseOptimGRAPE>());
context.RegisterService<xacc::Optimizer>(std::make_shared<xacc::PulseOptimGRAPE>());
}
void Stop(BundleContext /*context*/) {}
......
......@@ -79,14 +79,13 @@ namespace quantum {
buffer->addExtraInfo("ir-transform", true);
accelerator->execute(buffer, nullptr);
// Debug
buffer->print();
// Step 1: Retrieve the system dynamics from the Accelerator backend
const std::string H0 = buffer->getInformation("static-H").as<std::string>();
const std::vector<std::string> controlOps = buffer->getInformation("control-H").as<std::vector<std::string>>();
const std::vector<std::string> controlChannels = buffer->getInformation("control-channels").as<std::vector<std::string>>();
assert(controlOps.size() == controlChannels.size());
const double backendDt = buffer->getInformation("dt").as<double>();
const std::string hamJsonStr = buffer->getInformation("ham-json").as<std::string>();
// Step 2: Compute the total unitary matrix
auto fuser = xacc::getService<GateFuser>("default");
......@@ -190,7 +189,8 @@ namespace quantum {
std::make_pair("static-H", H0),
// Max time
std::make_pair("max-time", tMax),
std::make_pair("dt", backendDt)
std::make_pair("dt", backendDt),
std::make_pair("hamiltonian-json", hamJsonStr)
};
auto optimizer = xacc::getOptimizer("quantum-control", pulseOptimConfigs);
......@@ -251,7 +251,7 @@ namespace quantum {
// If "GRAPE" (or other time-series methods),
// we already optimize the pulses as data arrays,
// hence just need to make pulses from those arrays.
if (method == "GRAPE")
else
{
program->clear();
const auto nbControls = controlOps.size();
......@@ -265,7 +265,7 @@ namespace quantum {
// Add optimized pulses
for (int pulseId = 0; pulseId < nbControls; ++pulseId)
{
const std::string pulse_name = "Grape_Optim_Pulse_" + std::to_string(pulseId);
const std::string pulse_name = method + "_Optim_Pulse_" + std::to_string(pulseId);
auto pulse = std::make_shared<xacc::quantum::Pulse>(pulse_name);
pulse->setChannel(controlChannels[pulseId]);
......
Supports Markdown
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