Skip to content

Commit b0f2f0b

Browse files
Add bindings to C++ expval, var, probs in Lightning (#214)
* Add pybind for PL::Measures::* * Add python tests * Update probs, expval, and var bindings w.r.t. vec-val QNode examples * Update interface * Add python tests * Update tests * Update methods and tests * Integrate probs * Integrate expval and var * Update bindings * Complete integration and tests * Update bindings * Update python tests * Add more tests * Fix codecov issues * Apply some minor changes * Update * Try to fix the issue with Wheel::Windows * Update * Update * Update for MSVC * Update measures pybind * Apply code review suggestions * Remove using namespace Util in Measures.hpp * Update CHANGELOG Co-authored-by: Chae-Yeun Park <[email protected]>
1 parent 293fa58 commit b0f2f0b

File tree

10 files changed

+744
-36
lines changed

10 files changed

+744
-36
lines changed

.github/CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
* Add C++ only benchmark for a given list of gates.
88
[(#199)](https://github.com/PennyLaneAI/pennylane-lightning/pull/199)
99

10+
* Add bindings to C++ expval, var, probs.
11+
[(#214)](https://github.com/PennyLaneAI/pennylane-lightning/pull/214)
12+
1013
### Breaking changes
1114

1215
### Improvements
@@ -34,7 +37,7 @@
3437

3538
This release contains contributions from (in alphabetical order):
3639

37-
Amintor Dusko, Chae-Yeun Park, Lee James O'Riordan
40+
Ali Asadi, Amintor Dusko, Chae-Yeun Park, Lee James O'Riordan
3841

3942
---
4043

pennylane_lightning/lightning_qubit.py

+155
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
This module contains the :class:`~.LightningQubit` class, a PennyLane simulator device that
1616
interfaces with C++ for fast linear algebra calculations.
1717
"""
18+
from typing import List
1819
from warnings import warn
1920

2021
import numpy as np
@@ -29,14 +30,17 @@
2930
import pennylane as qml
3031
from pennylane.devices import DefaultQubit
3132
from pennylane.operation import Expectation
33+
from pennylane.wires import Wires
3234

3335
from ._version import __version__
3436

3537
try:
3638
from .lightning_qubit_ops import (
39+
MeasuresC64,
3740
StateVectorC64,
3841
AdjointJacobianC64,
3942
VectorJacobianProductC64,
43+
MeasuresC128,
4044
StateVectorC128,
4145
AdjointJacobianC128,
4246
VectorJacobianProductC128,
@@ -493,6 +497,157 @@ def batch_vjp(
493497

494498
return jacs, vjps
495499

500+
def probability(self, wires=None, shot_range=None, bin_size=None):
501+
"""Return the probability of each computational basis state.
502+
503+
Devices that require a finite number of shots always return the
504+
estimated probability.
505+
506+
Args:
507+
wires (Iterable[Number, str], Number, str, Wires): wires to return
508+
marginal probabilities for. Wires not provided are traced out of the system.
509+
shot_range (tuple[int]): 2-tuple of integers specifying the range of samples
510+
to use. If not specified, all samples are used.
511+
bin_size (int): Divides the shot range into bins of size ``bin_size``, and
512+
returns the measurement statistic separately over each bin. If not
513+
provided, the entire shot range is treated as a single bin.
514+
515+
Returns:
516+
array[float]: list of the probabilities
517+
"""
518+
if self.shots is not None:
519+
return self.estimate_probability(wires=wires, shot_range=shot_range, bin_size=bin_size)
520+
521+
wires = wires or self.wires
522+
wires = Wires(wires)
523+
524+
# translate to wire labels used by device
525+
device_wires = self.map_wires(wires)
526+
527+
# To support np.complex64 based on the type of self._state
528+
dtype = self._state.dtype
529+
if dtype == np.complex64:
530+
use_csingle = True
531+
elif dtype == np.complex128:
532+
use_csingle = False
533+
else:
534+
raise TypeError(f"Unsupported complex Type: {dtype}")
535+
536+
# Initialization of state
537+
ket = np.ravel(self._state)
538+
539+
if use_csingle:
540+
ket = ket.astype(np.complex64)
541+
542+
state_vector = StateVectorC64(ket) if use_csingle else StateVectorC128(ket)
543+
M = MeasuresC64(state_vector) if use_csingle else MeasuresC128(state_vector)
544+
545+
return M.probs(device_wires)
546+
547+
def expval(self, observable, shot_range=None, bin_size=None):
548+
"""Expectation value of the supplied observable.
549+
550+
Args:
551+
observable: A PennyLane observable.
552+
shot_range (tuple[int]): 2-tuple of integers specifying the range of samples
553+
to use. If not specified, all samples are used.
554+
bin_size (int): Divides the shot range into bins of size ``bin_size``, and
555+
returns the measurement statistic separately over each bin. If not
556+
provided, the entire shot range is treated as a single bin.
557+
558+
Returns:
559+
Expectation value of the observable
560+
"""
561+
if isinstance(observable.name, List) or observable.name in [
562+
"Identity",
563+
"Projector",
564+
"Hermitian",
565+
"Hamiltonian",
566+
"SparseHamiltonian",
567+
]:
568+
# TODO: requires backend support
569+
return super().expval(observable, shot_range=shot_range, bin_size=bin_size)
570+
571+
if self.shots is not None:
572+
# estimate the expectation value
573+
# TODO: Lightning support for sampling
574+
samples = self.sample(observable, shot_range=shot_range, bin_size=bin_size)
575+
return np.squeeze(np.mean(samples, axis=0))
576+
577+
# To support np.complex64 based on the type of self._state
578+
dtype = self._state.dtype
579+
if dtype == np.complex64:
580+
use_csingle = True
581+
elif dtype == np.complex128:
582+
use_csingle = False
583+
else:
584+
raise TypeError(f"Unsupported complex Type: {dtype}")
585+
586+
# Initialization of state
587+
ket = np.ravel(self._pre_rotated_state)
588+
589+
if use_csingle:
590+
ket = ket.astype(np.complex64)
591+
592+
state_vector = StateVectorC64(ket) if use_csingle else StateVectorC128(ket)
593+
M = MeasuresC64(state_vector) if use_csingle else MeasuresC128(state_vector)
594+
595+
# translate to wire labels used by device
596+
observable_wires = self.map_wires(observable.wires)
597+
598+
return M.expval(observable.name, observable_wires)
599+
600+
def var(self, observable, shot_range=None, bin_size=None):
601+
"""Variance of the supplied observable.
602+
603+
Args:
604+
observable: A PennyLane observable.
605+
shot_range (tuple[int]): 2-tuple of integers specifying the range of samples
606+
to use. If not specified, all samples are used.
607+
bin_size (int): Divides the shot range into bins of size ``bin_size``, and
608+
returns the measurement statistic separately over each bin. If not
609+
provided, the entire shot range is treated as a single bin.
610+
611+
Returns:
612+
Variance of the observable
613+
"""
614+
if isinstance(observable.name, List) or observable.name in [
615+
"Identity",
616+
"Projector",
617+
"Hermitian",
618+
]:
619+
# TODO: requires backend support
620+
return super().var(observable, shot_range=shot_range, bin_size=bin_size)
621+
622+
if self.shots is not None:
623+
# estimate the var
624+
# TODO: Lightning support for sampling
625+
samples = self.sample(observable, shot_range=shot_range, bin_size=bin_size)
626+
return np.squeeze(np.var(samples, axis=0))
627+
628+
# To support np.complex64 based on the type of self._state
629+
dtype = self._state.dtype
630+
if dtype == np.complex64:
631+
use_csingle = True
632+
elif dtype == np.complex128:
633+
use_csingle = False
634+
else:
635+
raise TypeError(f"Unsupported complex Type: {dtype}")
636+
637+
# Initialization of state
638+
ket = np.ravel(self._pre_rotated_state)
639+
640+
if use_csingle:
641+
ket = ket.astype(np.complex64)
642+
643+
state_vector = StateVectorC64(ket) if use_csingle else StateVectorC128(ket)
644+
M = MeasuresC64(state_vector) if use_csingle else MeasuresC128(state_vector)
645+
646+
# translate to wire labels used by device
647+
observable_wires = self.map_wires(observable.wires)
648+
649+
return M.var(observable.name, observable_wires)
650+
496651

497652
if not CPP_BINARY_AVAILABLE:
498653

pennylane_lightning/src/bindings/Bindings.cpp

+24
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,30 @@ void lightning_class_bindings(py::module &m) {
291291
return py::make_tuple(py::array_t<ParamT>(py::cast(jac)),
292292
py::array_t<ParamT>(py::cast(vjp_res)));
293293
});
294+
295+
//***********************************************************************//
296+
// Measures
297+
//***********************************************************************//
298+
299+
class_name = "MeasuresC" + bitsize;
300+
py::class_<Measures<PrecisionT>>(m, class_name.c_str())
301+
.def(py::init<const StateVectorRaw<PrecisionT> &>())
302+
.def("probs",
303+
[](Measures<PrecisionT> &M, const std::vector<size_t> &wires) {
304+
if (wires.empty()) {
305+
return py::array_t<ParamT>(py::cast(M.probs()));
306+
}
307+
return py::array_t<ParamT>(py::cast(M.probs(wires)));
308+
})
309+
.def("expval",
310+
[](Measures<PrecisionT> &M, const std::string &operation,
311+
const std::vector<size_t> &wires) {
312+
return M.expval(operation, wires);
313+
})
314+
.def("var", [](Measures<PrecisionT> &M, const std::string &operation,
315+
const std::vector<size_t> &wires) {
316+
return M.var(operation, wires);
317+
});
294318
}
295319

296320
/**

pennylane_lightning/src/bindings/Bindings.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
#include "AdjointDiff.hpp"
2121
#include "IndicesUtil.hpp"
2222
#include "JacobianProd.hpp"
23+
#include "Measures.hpp"
2324
#include "StateVectorRaw.hpp"
25+
2426
#include "pybind11/complex.h"
2527
#include "pybind11/numpy.h"
2628
#include "pybind11/pybind11.h"

pennylane_lightning/src/simulator/DynamicDispatcher.hpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,11 @@ template <typename fp_t> class DynamicDispatcher {
6161
std::unordered_map<std::string, KernelType> kernel_map_;
6262

6363
std::unordered_map<std::pair<std::string, KernelType>, Func,
64-
Internal::PairHash>
64+
Pennylane::Internal::PairHash>
6565
gates_;
6666

6767
DynamicDispatcher() {
68+
namespace Internal = Pennylane::Internal;
6869
for (const auto &[gate_op, n_wires] : Constant::gate_wires) {
6970
gate_wires_.emplace(lookup(Constant::gate_names, gate_op), n_wires);
7071
}
@@ -234,6 +235,7 @@ constexpr auto gateOpToFunctor() {
234235
return [](std::complex<PrecisionT> *data, size_t num_qubits,
235236
const std::vector<size_t> &wires, bool inverse,
236237
const std::vector<PrecisionT> &params) {
238+
namespace Internal = Pennylane::Internal;
237239
constexpr size_t num_params =
238240
static_lookup<gate_op>(Constant::gate_num_params);
239241
constexpr auto func_ptr = static_lookup<gate_op>(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2021 Xanadu Quantum Technologies Inc.
2+
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "Measures.hpp"
16+
17+
// explicit instantiation
18+
template class Pennylane::Measures<float, Pennylane::StateVectorRaw<float>>;
19+
template class Pennylane::Measures<double, Pennylane::StateVectorRaw<double>>;

0 commit comments

Comments
 (0)