Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass control values to the L-Qubit kernels #576

Merged
merged 16 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Release 0.34.0-dev

### New features since last release

* Lightning-Qubit support arbitrary controlled gates (any wires and any control values). The kernels are implemented in the `LM` module.
[(#576)](https://github.com/PennyLaneAI/pennylane-lightning/pull/576)

* Shot-noise related methods now accommodate observable objects with arbitrary eigenvalues. Add a Kronecker product method for two diagonal matrices.
[(#570)](https://github.com/PennyLaneAI/pennylane-lightning/pull/570)

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/wheel_win_x86_64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
fail-fast: false
matrix:
os: [windows-2022]
exec_model: ${{ fromJson(needs.set_wheel_build_matrix.outputs.exec_model) }}
exec_model: [\"SERIAL\"]
kokkos_version: ${{ fromJson(needs.set_wheel_build_matrix.outputs.kokkos_version) }}
timeout-minutes: 30
name: Kokkos core (${{ matrix.exec_model }})
Expand Down Expand Up @@ -88,9 +88,9 @@ jobs:
matrix:
os: [windows-2022]
arch: [AMD64]
pl_backend: ["lightning_qubit"]
pl_backend: ["lightning_kokkos", "lightning_qubit"]
cibw_build: ${{ fromJson(needs.set_wheel_build_matrix.outputs.python_version) }}
exec_model: ${{ fromJson(needs.set_wheel_build_matrix.outputs.exec_model) }}
exec_model: [\"SERIAL\"]
kokkos_version: ${{ fromJson(needs.set_wheel_build_matrix.outputs.kokkos_version) }}
timeout-minutes: 30
name: ${{ matrix.os }} - ${{ matrix.pl_backend }} (Python ${{ fromJson('{ "cp39-*":"3.9","cp310-*":"3.10","cp311-*":"3.11" }')[matrix.cibw_build] }})
Expand Down Expand Up @@ -183,7 +183,7 @@ jobs:
strategy:
matrix:
arch: [AMD64]
pl_backend: ["lightning_qubit"]
pl_backend: ["lightning_kokkos", "lightning_qubit"]
runs-on: ubuntu-latest

if: ${{ github.event_name == 'release' || github.ref == 'refs/heads/master'}}
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ format-python:
.PHONY: check-tidy
check-tidy:
rm -rf ./BuildTidy
cmake -BBuildTidy -DENABLE_CLANG_TIDY=ON -DBUILD_TESTS=ON -DENABLE_WARNINGS=ON -DPL_BACKEND=$(PL_BACKEND) $(OPTIONS)
cmake -BBuildTidy -DENABLE_CLANG_TIDY=ON -DBUILD_TESTS=ON -DENABLE_WARNINGS=ON -DCLANG_TIDY_BINARY=clang-tidy -DPL_BACKEND=$(PL_BACKEND) $(OPTIONS)
ifdef target
cmake --build ./BuildTidy $(VERBOSE) --target $(target)
else
Expand Down
43 changes: 35 additions & 8 deletions pennylane_lightning/core/_serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,15 @@ def serialize_observables(self, tape: QuantumTape, wires_map: dict) -> List:

def serialize_ops(
self, tape: QuantumTape, wires_map: dict
) -> Tuple[List[List[str]], List[np.ndarray], List[List[int]], List[bool], List[np.ndarray]]:
) -> Tuple[
List[List[str]],
List[np.ndarray],
List[List[int]],
List[bool],
List[np.ndarray],
List[List[int]],
List[List[bool]],
]:
"""Serializes the operations of an input tape.

The state preparation operations are not included.
Expand All @@ -313,30 +321,34 @@ def serialize_ops(
names = []
params = []
controlled_wires = []
controlled_values = []
wires = []
mats = []

uses_stateprep = False

def get_wires(operation, single_op):
if operation.name[0:2] == "C(" or (
operation.name == "MultiControlledX"
and all(char == "1" for char in operation.hyperparameters["control_values"])
):
if operation.name[0:2] == "C(" or operation.name == "MultiControlledX":
name = "PauliX" if operation.name == "MultiControlledX" else operation.base.name
controlled_wires_list = operation.control_wires
if operation.name == "MultiControlledX":
wires_list = list(set(operation.wires) - set(controlled_wires_list))
else:
wires_list = operation.target_wires
control_values_list = (
[bool(int(i)) for i in operation.hyperparameters["control_values"]]
if operation.name == "MultiControlledX"
else operation.control_values
)
if not hasattr(self.sv_type, name):
single_op = QubitUnitary(matrix(single_op.base), single_op.base.wires)
name = single_op.name
else:
name = single_op.name
wires_list = single_op.wires.tolist()
controlled_wires_list = []
return single_op, name, wires_list, controlled_wires_list
control_values_list = []
return single_op, name, wires_list, controlled_wires_list, control_values_list

for operation in tape.operations:
if isinstance(operation, (BasisState, StatePrep)):
Expand All @@ -348,7 +360,13 @@ def get_wires(operation, single_op):
op_list = [operation]

for single_op in op_list:
single_op, name, wires_list, controlled_wires_list = get_wires(operation, single_op)
(
single_op,
name,
wires_list,
controlled_wires_list,
controlled_values_list,
) = get_wires(operation, single_op)
names.append(name)
# QubitUnitary is a special case, it has a parameter which is not differentiable.
# We thus pass a dummy 0.0 parameter which will not be referenced
Expand All @@ -362,8 +380,17 @@ def get_wires(operation, single_op):
params.append(single_op.parameters)
mats.append([])

controlled_values.append(controlled_values_list)
controlled_wires.append([wires_map[w] for w in controlled_wires_list])
wires.append([wires_map[w] for w in wires_list])

inverses = [False] * len(names)
return (names, params, wires, inverses, mats, controlled_wires), uses_stateprep
return (
names,
params,
wires,
inverses,
mats,
controlled_wires,
controlled_values,
), uses_stateprep
2 changes: 1 addition & 1 deletion pennylane_lightning/core/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
Version number (major.minor.patch[-label])
"""

__version__ = "0.34.0-dev18"
__version__ = "0.34.0-dev19"
20 changes: 13 additions & 7 deletions pennylane_lightning/core/src/algorithms/AdjointJacobianBase.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ template <class StateVectorT, class Derived> class AdjointJacobianBase {
operations.getOpsParams()[op_idx],
operations.getOpsMatrices()[op_idx]);
} else {
state.applyOperation(operations.getOpsName()[op_idx],
operations.getOpsControlledWires()[op_idx],
operations.getOpsWires()[op_idx],
operations.getOpsInverses()[op_idx] ^ adj,
operations.getOpsParams()[op_idx],
operations.getOpsMatrices()[op_idx]);
state.applyOperation(
operations.getOpsName()[op_idx],
operations.getOpsControlledWires()[op_idx],
operations.getOpsControlledValues()[op_idx],
operations.getOpsWires()[op_idx],
operations.getOpsInverses()[op_idx] ^ adj,
operations.getOpsParams()[op_idx],
operations.getOpsMatrices()[op_idx]);
}
}
}
Expand All @@ -94,6 +96,7 @@ template <class StateVectorT, class Derived> class AdjointJacobianBase {
} else {
state.applyOperation(operations.getOpsName()[op_idx],
operations.getOpsControlledWires()[op_idx],
operations.getOpsControlledValues()[op_idx],
operations.getOpsWires()[op_idx],
!operations.getOpsInverses()[op_idx],
operations.getOpsParams()[op_idx],
Expand Down Expand Up @@ -141,15 +144,18 @@ template <class StateVectorT, class Derived> class AdjointJacobianBase {
* @param sv Statevector data to operate upon.
* @param op_name Name of parametric gate.
* @param controlled_wires Control wires.
* @param controlled_values Control values (false or true).
* @param wires Wires to operate upon.
* @param adj Indicate whether to take the adjoint of the operation.
* @return PrecisionT Generator scaling coefficient.
*/
inline auto applyGenerator(StateVectorT &sv, const std::string &op_name,
const std::vector<size_t> &controlled_wires,
const std::vector<bool> &controlled_values,
const std::vector<size_t> &wires, const bool adj)
-> PrecisionT {
return sv.applyGenerator(op_name, controlled_wires, wires, adj);
return sv.applyGenerator(op_name, controlled_wires, controlled_values,
wires, adj);
}

/**
Expand Down
23 changes: 19 additions & 4 deletions pennylane_lightning/core/src/algorithms/JacobianData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ template <class StateVectorT> class OpsData {
const std::vector<bool> ops_inverses_;
const std::vector<std::vector<ComplexT>> ops_matrices_;
const std::vector<std::vector<size_t>> ops_controlled_wires_;
const std::vector<std::vector<bool>> ops_controlled_values_;

public:
/**
Expand All @@ -64,18 +65,21 @@ template <class StateVectorT> class OpsData {
* @param ops_matrices Numerical representation of given matrix if not
* supported.
* @param ops_controlled_wires Control wires
* @param ops_controlled_wires Control values
*/
OpsData(std::vector<std::string> ops_name,
const std::vector<std::vector<PrecisionT>> &ops_params,
std::vector<std::vector<size_t>> ops_wires,
std::vector<bool> ops_inverses,
std::vector<std::vector<ComplexT>> ops_matrices,
std::vector<std::vector<size_t>> ops_controlled_wires)
std::vector<std::vector<size_t>> ops_controlled_wires,
std::vector<std::vector<bool>> ops_controlled_values)
: num_par_ops_{0}, ops_name_{std::move(ops_name)},
ops_params_{ops_params}, ops_wires_{std::move(ops_wires)},
ops_inverses_{std::move(ops_inverses)},
ops_matrices_{std::move(ops_matrices)},
ops_controlled_wires_{std::move(ops_controlled_wires)} {
ops_controlled_wires_{std::move(ops_controlled_wires)},
ops_controlled_values_{std::move(ops_controlled_values)} {
for (const auto &p : ops_params) {
num_par_ops_ += static_cast<size_t>(!p.empty());
}
Expand Down Expand Up @@ -103,7 +107,8 @@ template <class StateVectorT> class OpsData {
ops_params_{ops_params}, ops_wires_{std::move(ops_wires)},
ops_inverses_{std::move(ops_inverses)},
ops_matrices_{std::move(ops_matrices)},
ops_controlled_wires_(ops_name.size()) {
ops_controlled_wires_(ops_name.size()),
ops_controlled_values_(ops_name.size()) {
for (const auto &p : ops_params) {
num_par_ops_ += static_cast<size_t>(!p.empty());
}
Expand All @@ -128,7 +133,8 @@ template <class StateVectorT> class OpsData {
ops_wires_{std::move(ops_wires)},
ops_inverses_{std::move(ops_inverses)},
ops_matrices_(ops_name.size()),
ops_controlled_wires_(ops_name.size()) {
ops_controlled_wires_(ops_name.size()),
ops_controlled_values_(ops_name.size()) {
for (const auto &p : ops_params) {
num_par_ops_ += static_cast<size_t>(!p.empty());
}
Expand Down Expand Up @@ -178,6 +184,15 @@ template <class StateVectorT> class OpsData {
-> const std::vector<std::vector<size_t>> & {
return ops_controlled_wires_;
}
/**
* @brief Get the controlled values for each operation.
*
* @return const std::vector<std::vector<size_t>>&
*/
[[nodiscard]] auto getOpsControlledValues() const
-> const std::vector<std::vector<bool>> & {
return ops_controlled_values_;
}
/**
* @brief Get the adjoint flag for each operation.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ template <typename TypeList> void testAdjointJacobian() {
std::vector<std::vector<size_t>> controls{
std::vector<size_t>(num_qubits - 1)};
std::iota(controls[0].begin(), controls[0].end(), 0);
auto ops = OpsData<StateVectorT>({"PhaseShift"}, {{p}},
{{num_qubits - 1}},
{false}, {{}}, controls);
std::vector<std::vector<bool>> control_values{
std::vector<bool>(num_qubits - 1, true)};
auto ops = OpsData<StateVectorT>(
{"PhaseShift"}, {{p}}, {{num_qubits - 1}}, {false},
{{}}, controls, control_values);

std::vector<ComplexT> cdata(1U << num_qubits);
cdata[cdata.size() - 2] =
Expand Down Expand Up @@ -411,7 +413,8 @@ template <typename TypeList> void testAdjointJacobian() {
std::vector<std::vector<ComplexT>>{
{}, {}, {}, cnot, {}, {}, {}, {}},
std::vector<std::vector<size_t>>{
{}, {}, {}, {}, {}, {}, {}, {}});
{}, {}, {}, {}, {}, {}, {}, {}},
std::vector<std::vector<bool>>{{}, {}, {}, {}, {}, {}, {}, {}});

JacobianData<StateVectorT> tape{
num_params, psi.getLength(), psi.getData(), {obs}, ops, tp};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,8 @@ template <typename TypeList> void testAdjointJacobian() {
std::vector<std::vector<ComplexT>>{
{}, {}, {}, cnot, {}, {}, {}, {}},
std::vector<std::vector<size_t>>{
{}, {}, {}, {}, {}, {}, {}, {}});
{}, {}, {}, {}, {}, {}, {}, {}},
std::vector<std::vector<bool>>{{}, {}, {}, {}, {}, {}, {}, {}});

JacobianDataMPI<StateVectorT> tape{num_params, psi, {obs}, ops, tp};

Expand Down
18 changes: 13 additions & 5 deletions pennylane_lightning/core/src/bindings/Bindings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,8 @@ void registerBackendAgnosticAlgorithms(py::module_ &m) {
const std::vector<std::vector<size_t>> &,
const std::vector<bool> &,
const std::vector<std::vector<ComplexT>> &,
const std::vector<std::vector<size_t>> &>())
const std::vector<std::vector<size_t>> &,
const std::vector<std::vector<bool>> &>())
.def("__repr__", [](const OpsData<StateVectorT> &ops) {
using namespace Pennylane::Util;
std::ostringstream ops_stream;
Expand All @@ -535,6 +536,8 @@ void registerBackendAgnosticAlgorithms(py::module_ &m) {
ops_stream << ", 'inv': " << ops.getOpsInverses()[op];
ops_stream << ", 'controlled_wires': "
<< ops.getOpsControlledWires()[op];
ops_stream << ", 'controlled_values': "
<< ops.getOpsControlledValues()[op];
ops_stream << ", 'wires': " << ops.getOpsWires()[op];
ops_stream << "}";
if (op < ops.getSize() - 1) {
Expand All @@ -555,7 +558,8 @@ void registerBackendAgnosticAlgorithms(py::module_ &m) {
const std::vector<std::vector<size_t>> &ops_wires,
const std::vector<bool> &ops_inverses,
const std::vector<np_arr_c> &ops_matrices,
const std::vector<std::vector<size_t>> &ops_controlled_wires) {
const std::vector<std::vector<size_t>> &ops_controlled_wires,
const std::vector<std::vector<bool>> &ops_controlled_values) {
std::vector<std::vector<ComplexT>> conv_matrices(
ops_matrices.size());
for (size_t op = 0; op < ops_name.size(); op++) {
Expand All @@ -567,9 +571,13 @@ void registerBackendAgnosticAlgorithms(py::module_ &m) {
std::vector<ComplexT>{m_ptr, m_ptr + m_buffer.size};
}
}
return OpsData<StateVectorT>{ops_name, ops_params,
ops_wires, ops_inverses,
conv_matrices, ops_controlled_wires};
return OpsData<StateVectorT>{ops_name,
ops_params,
ops_wires,
ops_inverses,
conv_matrices,
ops_controlled_wires,
ops_controlled_values};
},
"Create a list of operations from data.");

Expand Down
13 changes: 9 additions & 4 deletions pennylane_lightning/core/src/bindings/BindingsMPI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,8 @@ void registerBackendAgnosticAlgorithmsMPI(py::module_ &m) {
const std::vector<std::vector<size_t>> &ops_wires,
const std::vector<bool> &ops_inverses,
const std::vector<np_arr_c> &ops_matrices,
const std::vector<std::vector<size_t>> &ops_controlled_wires) {
const std::vector<std::vector<size_t>> &ops_controlled_wires,
const std::vector<std::vector<bool>> &ops_controlled_values) {
std::vector<std::vector<ComplexT>> conv_matrices(
ops_matrices.size());
for (size_t op = 0; op < ops_name.size(); op++) {
Expand All @@ -385,9 +386,13 @@ void registerBackendAgnosticAlgorithmsMPI(py::module_ &m) {
std::vector<ComplexT>{m_ptr, m_ptr + m_buffer.size};
}
}
return OpsData<StateVectorT>{ops_name, ops_params,
ops_wires, ops_inverses,
conv_matrices, ops_controlled_wires};
return OpsData<StateVectorT>{ops_name,
ops_params,
ops_wires,
ops_inverses,
conv_matrices,
ops_controlled_wires,
ops_controlled_values};
},
"Create a list of operations from data.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,11 @@ template <typename TypeList> void testApplyOperations() {
reinterpret_cast<ComplexT *>(st_data_2.data()),
st_data_2.size());

state_vector_1.applyOperation("PauliX", {}, {0}, false);
state_vector_1.applyOperation("PauliY", {}, {1}, false);
state_vector_1.applyOperation("PauliX", {}, {}, {0}, false);
state_vector_1.applyOperation("PauliY", {}, {}, {1}, false);

state_vector_2.applyOperation("PauliX", {}, {0}, false);
state_vector_2.applyOperation("PauliY", {}, {1}, false);
state_vector_2.applyOperation("PauliX", {}, {}, {0}, false);
state_vector_2.applyOperation("PauliY", {}, {}, {1}, false);

REQUIRE(isApproxEqual(
state_vector_1.getData(), state_vector_1.getLength(),
Expand Down
Loading
Loading