Skip to content

Commit

Permalink
Merge pull request #126 from qulacs/118-probablistic
Browse files Browse the repository at this point in the history
Probablistic Gate
  • Loading branch information
KowerKoint authored Jul 2, 2024
2 parents 72de7b0 + 773cbdc commit db969b9
Show file tree
Hide file tree
Showing 11 changed files with 295 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/cpu/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"/usr/local/include/kokkos",
"build/_deps/eigen_fetch-src",
"build/_deps/googletest_fetch-src/googletest/include",
"~/.local/lib/python3.10/dist-packages/nanobind/include",
"~/.local/lib/python3.10/site-packages/nanobind/include",
"/usr/include/python3.10"
],
"C_Cpp.default.cppStandard": "c++20"
Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/gpu/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"/usr/local/include/kokkos",
"build/_deps/eigen_fetch-src",
"build/_deps/googletest_fetch-src/googletest/include",
"~/.local/lib/python3.10/dist-packages/nanobind/include",
"~/.local/lib/python3.10/site-packages/nanobind/include",
"/usr/include/python3.10"
],
"C_Cpp.default.cppStandard": "c++20"
Expand Down
1 change: 1 addition & 0 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ nanobind_add_stub(
MODULE scaluq.scaluq_core.gate
OUTPUT scaluq/gate.pyi
PYTHON_PATH $<TARGET_FILE_DIR:scaluq>
MARKER_FILE scaluq/py.typed
VERBOSE
)
50 changes: 48 additions & 2 deletions python/binding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,18 @@ NB_MODULE(scaluq_core, m) {
"Specific class of multi-qubit pauli-rotation gate, represented as "
"$e^{-i\\frac{\\mathrm{angle}}{2}P}$.");

DEF_GATE(ProbablisticGate,
"Specific class of probablistic gate. The gate to apply is picked from a cirtain "
"distribution.")
.def(
"gate_list",
[](const ProbablisticGate &gate) { return gate->gate_list(); },
nb::rv_policy::reference)
.def(
"distribution",
[](const ProbablisticGate &gate) { return gate->distribution(); },
nb::rv_policy::reference);

auto mgate = m.def_submodule("gate", "Define gates.");

#define DEF_GATE_FACTORY(GATE_NAME) \
Expand Down Expand Up @@ -552,6 +564,7 @@ NB_MODULE(scaluq_core, m) {
DEF_GATE_FACTORY(FusedSwap);
DEF_GATE_FACTORY(Pauli);
DEF_GATE_FACTORY(PauliRotation);
DEF_GATE_FACTORY(Probablistic);

nb::enum_<ParamGateType>(m, "ParamGateType", "Enum of ParamGate Type.")
.value("PRX", ParamGateType::PRX)
Expand Down Expand Up @@ -631,6 +644,19 @@ NB_MODULE(scaluq_core, m) {
"Specific class of parametric multi-qubit pauli-rotation gate, represented as "
"$e^{-i\\frac{\\mathrm{angle}}{2}P}$. `angle` is given as `param * pcoef`.");

DEF_PGATE(PProbablisticGate,
"Specific class of parametric probablistic gate. The gate to apply is picked from a "
"cirtain "
"distribution.")
.def(
"gate_list",
[](const PProbablisticGate &gate) { return gate->gate_list(); },
nb::rv_policy::reference)
.def(
"distribution",
[](const PProbablisticGate &gate) { return gate->distribution(); },
nb::rv_policy::reference);

mgate.def("PRX",
&gate::PRX,
"Generate general ParamGate class instance of PRX.",
Expand All @@ -651,11 +677,31 @@ NB_MODULE(scaluq_core, m) {
"Generate general ParamGate class instance of PPauliRotation.",
"pauli"_a,
"coef"_a = 1.);
mgate.def("PProbablistic",
&gate::PProbablistic,
"Generate general ParamGate class instance of PProbablistic.");
mgate.def(
"PProbablistic",
[](const std::vector<std::pair<double, std::variant<Gate, ParamGate>>> &prob_gate_list) {
std::vector<double> distribution;
std::vector<std::variant<Gate, ParamGate>> gate_list;
distribution.reserve(prob_gate_list.size());
gate_list.reserve(prob_gate_list.size());
for (const auto &[prob, gate] : prob_gate_list) {
distribution.push_back(prob);
gate_list.push_back(gate);
}
return gate::PProbablistic(distribution, gate_list);
},
"Generate general ParamGate class instance of PProbablistic.");

nb::class_<Circuit>(m, "Circuit", "Quantum circuit represented as gate array")
.def(nb::init<UINT>(), "Initialize empty circuit of specified qubits.")
.def("n_qubits", &Circuit::n_qubits, "Get property of `n_qubits`.")
.def("gate_list", &Circuit::gate_list, "Get property of `gate_list`.")
.def("gate_list",
&Circuit::gate_list,
"Get property of `gate_list`.",
nb::rv_policy::reference)
.def("gate_count", &Circuit::gate_count, "Get property of `gate_count`.")
.def("key_set", &Circuit::key_set, "Get set of keys of parameters.")
.def("get", &Circuit::get, "Get reference of i-th gate.")
Expand Down Expand Up @@ -813,7 +859,7 @@ NB_MODULE(scaluq_core, m) {
.def(nb::init<UINT>())
.def("is_hermitian", &Operator::is_hermitian)
.def("n_qubits", &Operator::n_qubits)
.def("terms", &Operator::terms)
.def("terms", &Operator::terms, nb::rv_policy::reference)
.def("to_string", &Operator::to_string)
.def("add_operator", nb::overload_cast<const PauliOperator &>(&Operator::add_operator))
.def(
Expand Down
1 change: 1 addition & 0 deletions python/scaluq/gate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .scaluq_core.gate import *
6 changes: 6 additions & 0 deletions scaluq/gate/gate_factory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "gate_one_control_one_target.hpp"
#include "gate_one_qubit.hpp"
#include "gate_pauli.hpp"
#include "gate_probablistic.hpp"
#include "gate_two_qubit.hpp"
#include "gate_zero_qubit.hpp"

Expand Down Expand Up @@ -103,5 +104,10 @@ inline Gate Pauli(const PauliOperator& pauli) {
inline Gate PauliRotation(const PauliOperator& pauli, double angle) {
return internal::GateFactory::create_gate<internal::PauliRotationGateImpl>(pauli, angle);
}
inline Gate Probablistic(const std::vector<double>& distribution,
const std::vector<Gate>& gate_list) {
return internal::GateFactory::create_gate<internal::ProbablisticGateImpl>(distribution,
gate_list);
}
} // namespace gate
} // namespace scaluq
86 changes: 86 additions & 0 deletions scaluq/gate/gate_probablistic.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#pragma once

#include "../util/random.hpp"
#include "gate.hpp"

namespace scaluq {
namespace internal {
class ProbablisticGateImpl : public GateBase {
std::vector<double> _distribution;
std::vector<double> _cumlative_distribution;
std::vector<Gate> _gate_list;

public:
ProbablisticGateImpl(const std::vector<double>& distribution,
const std::vector<Gate>& gate_list)
: _distribution(distribution), _gate_list(gate_list) {
UINT n = distribution.size();
if (n == 0) {
throw std::runtime_error("At least one gate is required.");
}
if (n != gate_list.size()) {
throw std::runtime_error("distribution and gate_list have different size.");
}
_cumlative_distribution.resize(n + 1);
std::partial_sum(
distribution.begin(), distribution.end(), _cumlative_distribution.begin() + 1);
if (std::abs(_cumlative_distribution.back() - 1.) > 1e-6) {
throw std::runtime_error("Sum of distribution must be equal to 1.");
}
}
const std::vector<Gate>& gate_list() { return _gate_list; }
const std::vector<double>& distribution() { return _distribution; }

std::vector<UINT> get_target_qubit_list() const override {
std::vector<UINT> ret;
for (const auto& gate : _gate_list) {
std::vector<UINT> targets = gate->get_target_qubit_list();
ret.reserve(ret.size() + targets.size());
std::ranges::copy(targets, std::back_inserter(ret));
}
std::ranges::sort(ret);
auto result = std::ranges::unique(ret);
ret.erase(result.begin(), result.end());
return ret;
}
std::vector<UINT> get_control_qubit_list() const override {
std::vector<UINT> ret;
for (const auto& gate : _gate_list) {
std::vector<UINT> controls = gate->get_control_qubit_list();
ret.reserve(ret.size() + controls.size());
std::ranges::copy(controls, std::back_inserter(ret));
}
std::ranges::sort(ret);
auto result = std::ranges::unique(ret);
ret.erase(result.begin(), result.end());
return ret;
}

Gate copy() const override { return std::make_shared<ProbablisticGateImpl>(*this); }
Gate get_inverse() const override {
std::vector<Gate> inv_gate_list;
inv_gate_list.reserve(_gate_list.size());
std::ranges::transform(_gate_list, std::back_inserter(inv_gate_list), [](const Gate& gate) {
return gate->get_inverse();
});
return std::make_shared<ProbablisticGateImpl>(_distribution, inv_gate_list);
}
std::optional<ComplexMatrix> get_matrix() const override {
if (_gate_list.size() == 1) return _gate_list[0]->get_matrix();
return std::nullopt;
}

void update_quantum_state(StateVector& state_vector) const override {
Random random;
double r = random.uniform();
UINT i = std::distance(_cumlative_distribution.begin(),
std::ranges::upper_bound(_cumlative_distribution, r)) -
1;
if (i >= _gate_list.size()) i = _gate_list.size() - 1;
_gate_list[i]->update_quantum_state(state_vector);
}
};
} // namespace internal

using ProbablisticGate = internal::GatePtr<internal::ProbablisticGateImpl>;
} // namespace scaluq
6 changes: 6 additions & 0 deletions scaluq/gate/param_gate_factory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "param_gate_one_qubit.hpp"
#include "param_gate_pauli.hpp"
#include "param_gate_probablistic.hpp"

namespace scaluq {
namespace internal {
Expand All @@ -26,5 +27,10 @@ inline ParamGate PRZ(UINT target, double pcoef = 1.) {
inline ParamGate PPauliRotation(const PauliOperator& pauli, double pcoef = 1.) {
return internal::ParamGateFactory::create_gate<internal::PPauliRotationGateImpl>(pauli, pcoef);
}
inline ParamGate PProbablistic(const std::vector<double>& distribution,
const std::vector<std::variant<Gate, ParamGate>>& gate_list) {
return internal::ParamGateFactory::create_gate<internal::PProbablisticGateImpl>(distribution,
gate_list);
}
} // namespace gate
} // namespace scaluq
105 changes: 105 additions & 0 deletions scaluq/gate/param_gate_probablistic.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#pragma once

#include <variant>

#include "../util/random.hpp"
#include "gate.hpp"
#include "param_gate.hpp"

namespace scaluq {
namespace internal {
class PProbablisticGateImpl : public ParamGateBase {
using EitherGate = std::variant<Gate, ParamGate>;
std::vector<double> _distribution;
std::vector<double> _cumlative_distribution;
std::vector<EitherGate> _gate_list;

public:
PProbablisticGateImpl(const std::vector<double>& distribution,
const std::vector<std::variant<Gate, ParamGate>>& gate_list)
: _distribution(distribution), _gate_list(gate_list) {
UINT n = distribution.size();
if (n == 0) {
throw std::runtime_error("At least one gate is required.");
}
if (n != gate_list.size()) {
throw std::runtime_error("distribution and gate_list have different size.");
}
_cumlative_distribution.resize(n + 1);
std::partial_sum(
distribution.begin(), distribution.end(), _cumlative_distribution.begin() + 1);
if (std::abs(_cumlative_distribution.back() - 1.) > 1e-6) {
throw std::runtime_error("Sum of distribution must be equal to 1.");
}
}
const std::vector<std::variant<Gate, ParamGate>>& gate_list() { return _gate_list; }
const std::vector<double>& distribution() { return _distribution; }

std::vector<UINT> get_target_qubit_list() const override {
std::vector<UINT> ret;
for (const auto& gate : _gate_list) {
std::vector<UINT> targets =
std::visit([](const auto& g) { return g->get_target_qubit_list(); }, gate);
ret.reserve(ret.size() + targets.size());
std::ranges::copy(targets, std::back_inserter(ret));
}
std::ranges::sort(ret);
auto result = std::ranges::unique(ret);
ret.erase(result.begin(), result.end());
return ret;
}
std::vector<UINT> get_control_qubit_list() const override {
std::vector<UINT> ret;
for (const auto& gate : _gate_list) {
std::vector<UINT> controls =
std::visit([](const auto& g) { return g->get_control_qubit_list(); }, gate);
ret.reserve(ret.size() + controls.size());
std::ranges::copy(controls, std::back_inserter(ret));
}
std::ranges::sort(ret);
auto result = std::ranges::unique(ret);
ret.erase(result.begin(), result.end());
return ret;
}

ParamGate copy() const override { return std::make_shared<PProbablisticGateImpl>(*this); }
ParamGate get_inverse() const override {
std::vector<EitherGate> inv_gate_list;
inv_gate_list.reserve(_gate_list.size());
std::ranges::transform(
_gate_list, std::back_inserter(inv_gate_list), [](const EitherGate& gate) {
return std::visit([](const auto& g) { return EitherGate{g->get_inverse()}; }, gate);
});
return std::make_shared<PProbablisticGateImpl>(_distribution, inv_gate_list);
}
std::optional<ComplexMatrix> get_matrix(double param) const override {
if (_gate_list.size() == 1) {
const auto& gate = _gate_list[0];
if (gate.index() == 0) {
return std::get<0>(gate)->get_matrix();
} else {
return std::get<1>(gate)->get_matrix(param);
}
}
return std::nullopt;
}

void update_quantum_state(StateVector& state_vector, double param) const override {
Random random;
double r = random.uniform();
UINT i = std::distance(_cumlative_distribution.begin(),
std::ranges::upper_bound(_cumlative_distribution, r)) -
1;
if (i >= _gate_list.size()) i = _gate_list.size() - 1;
const auto& gate = _gate_list[i];
if (gate.index() == 0) {
std::get<0>(gate)->update_quantum_state(state_vector);
} else {
std::get<1>(gate)->update_quantum_state(state_vector, param);
}
}
};
} // namespace internal

using PProbablisticGate = internal::ParamGatePtr<internal::PProbablisticGateImpl>;
} // namespace scaluq
20 changes: 20 additions & 0 deletions tests/gate/gate_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,23 @@ TEST(GateTest, ApplyFused) {
}

TEST(GateTest, ApplyPauliGate) { run_random_gate_apply_pauli(5); }

TEST(GateTest, ApplyProbablisticGate) {
auto probgate = gate::Probablistic({.1, .9}, {gate::X(0), gate::I()});
UINT x_cnt = 0, i_cnt = 0;
StateVector state(1);
for (auto _ : std::views::iota(0, 100)) {
UINT before = state.sampling(1)[0];
probgate->update_quantum_state(state);
UINT after = state.sampling(1)[0];
if (before != after) {
x_cnt++;
} else {
i_cnt++;
}
}
// These test is probablistic, but pass at least 99.99% cases.
ASSERT_GT(x_cnt, 0);
ASSERT_GT(i_cnt, 0);
ASSERT_LT(x_cnt, i_cnt);
}
20 changes: 20 additions & 0 deletions tests/gate/param_gate_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,23 @@ TEST(ParamGateTest, ApplyPRZGate) {
test_apply_parametric_single_pauli_rotation(5, &gate::RX, &gate::PRX);
}
TEST(ParamGateTest, ApplyPPauliRotationGate) { test_apply_parametric_multi_pauli_rotation(5); }

TEST(ParamGateTest, ApplyPProbablisticGate) {
auto probgate = gate::PProbablistic({.1, .9}, {gate::PRX(0), gate::I()});
UINT x_cnt = 0, i_cnt = 0;
StateVector state(1);
for (auto _ : std::views::iota(0, 100)) {
UINT before = state.sampling(1)[0];
probgate->update_quantum_state(state, scaluq::PI());
UINT after = state.sampling(1)[0];
if (before != after) {
x_cnt++;
} else {
i_cnt++;
}
}
// These test is probablistic, but pass at least 99.99% cases.
ASSERT_GT(x_cnt, 0);
ASSERT_GT(i_cnt, 0);
ASSERT_LT(x_cnt, i_cnt);
}

0 comments on commit db969b9

Please sign in to comment.