Program Listing for File DynamicDispatcher.hpp

Return to documentation for file (pennylane_lightning/core/src/simulators/lightning_qubit/gates/DynamicDispatcher.hpp)

// Copyright 2018-2023 Xanadu Quantum Technologies Inc.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once

#include <complex>
#include <functional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <variant>
#include <vector>

#include "Constant.hpp"
#include "ConstantUtil.hpp" // lookup
#include "Error.hpp"
#include "GateIndices.hpp"
#include "KernelType.hpp"
#include "Macros.hpp"
#include "OpToMemberFuncPtr.hpp"
#include "Util.hpp" // PairHash, exp2

namespace {
namespace GateConstant = Pennylane::Gates::Constant;
using Pennylane::Gates::GateOperation;
using Pennylane::Gates::GeneratorOperation;
using Pennylane::Gates::KernelType;
using Pennylane::Gates::MatrixOperation;
using Pennylane::Util::exp2;
using Pennylane::Util::lookup;
using Pennylane::Util::PairHash;
} // namespace

namespace Pennylane::LightningQubit::Internal {
constexpr auto generatorNamesWithoutPrefix() {
    namespace GateConstant = Pennylane::Gates::Constant;
    std::array<std::pair<GeneratorOperation, std::string_view>,
               GateConstant::generator_names.size()>
        res{};
    for (size_t i = 0; i < GateConstant::generator_names.size(); i++) {
        // NOLINTBEGIN(cppcoreguidelines-pro-bounds-constant-array-index)
        const auto [gntr_op, gntr_name] = GateConstant::generator_names[i];
        res[i].first = gntr_op;
        res[i].second = gntr_name.substr(0);
        // NOLINTEND(cppcoreguidelines-pro-bounds-constant-array-index)
    }
    return res;
}

} // namespace Pennylane::LightningQubit::Internal

namespace Pennylane::LightningQubit {
template <typename PrecisionT> class DynamicDispatcher {
  public:
    using CFP_t = std::complex<PrecisionT>;

    using GateFunc = std::function<void(
        std::complex<PrecisionT> * /*data*/, std::size_t /*num_qubits*/,
        const std::vector<std::size_t> & /*wires*/, bool /*inverse*/,
        const std::vector<PrecisionT> & /*params*/)>;
    using ControlledGateFunc = std::function<void(
        std::complex<PrecisionT> * /*data*/, std::size_t /*num_qubits*/,
        const std::vector<std::size_t> & /*controlled_wires*/,
        const std::vector<bool> & /*controlled_values*/,
        const std::vector<std::size_t> & /*wires*/, bool /*inverse*/,
        const std::vector<PrecisionT> & /*params*/)>;

    using GeneratorFunc = Gates::GeneratorFuncPtrT<PrecisionT>;
    using ControlledGeneratorFunc =
        Gates::ControlledGeneratorFuncPtrT<PrecisionT>;

    using MatrixFunc = Gates::MatrixFuncPtrT<PrecisionT>;
    using ControlledMatrixFunc = Gates::ControlledMatrixFuncPtrT<PrecisionT>;

  private:
    std::unordered_map<std::string, GateOperation> str_to_gates_{};

    std::unordered_map<std::string, GeneratorOperation> str_to_gntrs_{};

    std::unordered_map<std::pair<GateOperation, KernelType>, GateFunc, PairHash>
        gate_kernels_{};

    std::unordered_map<std::pair<GeneratorOperation, KernelType>, GeneratorFunc,
                       PairHash>
        generator_kernels_{};

    std::unordered_map<std::pair<MatrixOperation, KernelType>, MatrixFunc,
                       PairHash>
        matrix_kernels_{};

    std::unordered_map<KernelType, std::string> kernel_names_{};

    std::unordered_map<std::string, ControlledGateOperation>
        str_to_controlled_gates_{};

    std::unordered_map<std::string, ControlledGeneratorOperation>
        str_to_controlled_gntrs_{};

    std::unordered_map<std::pair<ControlledGateOperation, KernelType>,
                       ControlledGateFunc, PairHash>
        controlled_gate_kernels_{};

    std::unordered_map<std::pair<ControlledGeneratorOperation, KernelType>,
                       ControlledGeneratorFunc, PairHash>
        controlled_generator_kernels_{};

    std::unordered_map<std::pair<ControlledMatrixOperation, KernelType>,
                       ControlledMatrixFunc, PairHash>
        controlled_matrix_kernels_{};

    DynamicDispatcher() {
        constexpr static auto gntr_names_without_prefix =
            Internal::generatorNamesWithoutPrefix();

        for (const auto &[gate_op, gate_name] : GateConstant::gate_names) {
            str_to_gates_.emplace(gate_name, gate_op);
        }
        for (const auto &[gntr_op, gntr_name] : gntr_names_without_prefix) {
            str_to_gntrs_.emplace(gntr_name, gntr_op);
        }
        for (const auto &[gate_op, gate_name] :
             GateConstant::controlled_gate_names) {
            str_to_controlled_gates_.emplace(gate_name, gate_op);
        }
        for (const auto &[gntr_op, gntr_name] :
             GateConstant::controlled_generator_names) {
            str_to_controlled_gntrs_.emplace(gntr_name, gntr_op);
        }
    }

  public:
    DynamicDispatcher(const DynamicDispatcher &) = delete;
    DynamicDispatcher(DynamicDispatcher &&) = delete;
    DynamicDispatcher &operator=(const DynamicDispatcher &) = delete;
    DynamicDispatcher &operator=(DynamicDispatcher &&) = delete;
    ~DynamicDispatcher() = default;

    static DynamicDispatcher &getInstance() {
        static DynamicDispatcher singleton;
        return singleton;
    }

    [[nodiscard]] auto registeredKernels() const -> std::vector<KernelType> {
        std::vector<KernelType> kernels;

        kernels.reserve(kernel_names_.size());
        for (const auto &[kernel, name] : kernel_names_) {
            kernels.emplace_back(kernel);
        }
        return kernels;
    }

    [[nodiscard]] auto isRegisteredKernel(KernelType kernel) const {
        return kernel_names_.contains(kernel);
    }

    void registerKernelName(KernelType kernel, std::string name) {
        kernel_names_.emplace(kernel, std::move(name));
    }

    [[nodiscard]] auto getKernelName(KernelType kernel) const -> std::string {
        return kernel_names_.at(kernel);
    }

    [[nodiscard]] auto registeredGatesForKernel(KernelType kernel) const
        -> std::unordered_set<GateOperation> {
        std::unordered_set<GateOperation> gates;

        for (const auto &[key, val] : gate_kernels_) {
            if (key.second == kernel) {
                gates.emplace(key.first);
            }
        }
        return gates;
    }

    [[nodiscard]] auto
    registeredControlledGatesForKernel(KernelType kernel) const
        -> std::unordered_set<ControlledGateOperation> {
        std::unordered_set<ControlledGateOperation> gates;

        for (const auto &[key, val] : controlled_gate_kernels_) {
            if (key.second == kernel) {
                gates.emplace(key.first);
            }
        }
        return gates;
    }

    [[nodiscard]] auto registeredGeneratorsForKernel(KernelType kernel) const
        -> std::unordered_set<GeneratorOperation> {
        std::unordered_set<GeneratorOperation> gntrs;

        for (const auto &[key, val] : generator_kernels_) {
            if (key.second == kernel) {
                gntrs.emplace(key.first);
            }
        }
        return gntrs;
    }

    [[nodiscard]] auto
    registeredControlledGeneratorsForKernel(KernelType kernel) const
        -> std::unordered_set<ControlledGeneratorOperation> {
        std::unordered_set<ControlledGeneratorOperation> generators;

        for (const auto &[key, val] : controlled_generator_kernels_) {
            if (key.second == kernel) {
                generators.emplace(key.first);
            }
        }
        return generators;
    }

    [[nodiscard]] auto registeredMatricesForKernel(KernelType kernel) const
        -> std::unordered_set<MatrixOperation> {
        std::unordered_set<MatrixOperation> matrices;

        for (const auto &[key, val] : matrix_kernels_) {
            if (key.second == kernel) {
                matrices.emplace(key.first);
            }
        }
        return matrices;
    }

    [[nodiscard]] auto
    registeredControlledMatricesForKernel(KernelType kernel) const
        -> std::unordered_set<ControlledMatrixOperation> {
        std::unordered_set<ControlledMatrixOperation> matrices;

        for (const auto &[key, val] : controlled_matrix_kernels_) {
            if (key.second == kernel) {
                matrices.emplace(key.first);
            }
        }
        return matrices;
    }

    [[nodiscard]] auto strToGateOp(const std::string &gate_name) const
        -> GateOperation {
        return str_to_gates_.at(gate_name);
    }

    [[nodiscard]] auto strToControlledGateOp(const std::string &gate_name) const
        -> ControlledGateOperation {
        return str_to_controlled_gates_.at(gate_name);
    }

    [[nodiscard]] auto hasGateOp(const std::string &gate_name) const -> bool {
        return str_to_gates_.contains(gate_name);
    }

    [[nodiscard]] auto strToGeneratorOp(const std::string &gntr_name) const
        -> GeneratorOperation {
        return str_to_gntrs_.at(gntr_name);
    }

    [[nodiscard]] auto
    strToControlledGeneratorOp(const std::string &gntr_name) const
        -> ControlledGeneratorOperation {
        return str_to_controlled_gntrs_.at(gntr_name);
    }

    template <typename FunctionType>
    void registerGateOperation(GateOperation gate_op, KernelType kernel,
                               FunctionType &&func) {
        gate_kernels_.emplace(std::make_pair(gate_op, kernel),
                              std::forward<FunctionType>(func));
    }

    template <typename FunctionType>
    void registerControlledGateOperation(ControlledGateOperation gate_op,
                                         KernelType kernel,
                                         FunctionType &&func) {
        controlled_gate_kernels_.emplace(std::make_pair(gate_op, kernel),
                                         std::forward<FunctionType>(func));
    }

    template <typename FunctionType>
    void registerGeneratorOperation(GeneratorOperation gntr_op,
                                    KernelType kernel, FunctionType &&func) {
        generator_kernels_.emplace(std::make_pair(gntr_op, kernel),
                                   std::forward<FunctionType>(func));
    }

    template <typename FunctionType>
    void
    registerControlledGeneratorOperation(ControlledGeneratorOperation gen_op,
                                         KernelType kernel,
                                         FunctionType &&func) {
        controlled_generator_kernels_.emplace(std::make_pair(gen_op, kernel),
                                              std::forward<FunctionType>(func));
    }

    void registerMatrixOperation(MatrixOperation mat_op, KernelType kernel,
                                 MatrixFunc func) {
        matrix_kernels_.emplace(std::make_pair(mat_op, kernel), func);
    }

    void registerControlledMatrixOperation(ControlledMatrixOperation mat_op,
                                           KernelType kernel,
                                           ControlledMatrixFunc func) {
        controlled_matrix_kernels_.emplace(std::make_pair(mat_op, kernel),
                                           func);
    }

    [[nodiscard]] bool isRegistered(GateOperation gate_op,
                                    KernelType kernel) const {
        return gate_kernels_.find(std::make_pair(gate_op, kernel)) !=
               gate_kernels_.cend();
    }

    bool isRegistered(ControlledGateOperation gate_op,
                      KernelType kernel) const {
        return controlled_gate_kernels_.find(std::make_pair(gate_op, kernel)) !=
               controlled_gate_kernels_.cend();
    }

    [[nodiscard]] bool isRegistered(GeneratorOperation gntr_op,
                                    KernelType kernel) const {
        return generator_kernels_.find(std::make_pair(gntr_op, kernel)) !=
               generator_kernels_.cend();
    }

    bool isRegistered(ControlledGeneratorOperation gen_op,
                      KernelType kernel) const {
        return controlled_generator_kernels_.find(std::make_pair(
                   gen_op, kernel)) != controlled_generator_kernels_.cend();
    }

    [[nodiscard]] bool isRegistered(MatrixOperation mat_op,
                                    KernelType kernel) const {
        return matrix_kernels_.find(std::make_pair(mat_op, kernel)) !=
               matrix_kernels_.cend();
    }

    bool isRegistered(ControlledMatrixOperation mat_op,
                      KernelType kernel) const {
        return controlled_matrix_kernels_.find(std::make_pair(
                   mat_op, kernel)) != controlled_matrix_kernels_.cend();
    }

    void applyOperation(KernelType kernel, CFP_t *data, std::size_t num_qubits,
                        const std::string &op_name,
                        const std::vector<std::size_t> &wires, bool inverse,
                        const std::vector<PrecisionT> &params = {}) const {
        applyOperation(kernel, data, num_qubits, strToGateOp(op_name), wires,
                       inverse, params);
    }

    void applyOperation(KernelType kernel, CFP_t *data, std::size_t num_qubits,
                        GateOperation gate_op,
                        const std::vector<std::size_t> &wires, bool inverse,
                        const std::vector<PrecisionT> &params = {}) const {
        const auto iter = gate_kernels_.find(std::make_pair(gate_op, kernel));
        PL_ABORT_IF(iter == gate_kernels_.cend(),
                    "Cannot find a registered kernel for a given gate "
                    "and kernel pair");
        (iter->second)(data, num_qubits, wires, inverse, params);
    }

    void applyControlledGate(KernelType kernel, CFP_t *data,
                             std::size_t num_qubits, const std::string &op_name,
                             const std::vector<std::size_t> &controlled_wires,
                             const std::vector<bool> &controlled_values,
                             const std::vector<std::size_t> &wires,
                             bool inverse,
                             const std::vector<PrecisionT> &params = {}) const {
        applyControlledGate(kernel, data, num_qubits,
                            strToControlledGateOp(op_name), controlled_wires,
                            controlled_values, wires, inverse, params);
    }

    void applyControlledGate(KernelType kernel, CFP_t *data,
                             std::size_t num_qubits,
                             const ControlledGateOperation op_name,
                             const std::vector<std::size_t> &controlled_wires,
                             const std::vector<bool> &controlled_values,
                             const std::vector<std::size_t> &wires,
                             bool inverse,
                             const std::vector<PrecisionT> &params = {}) const {
        PL_ABORT_IF_NOT(controlled_wires.size() == controlled_values.size(),
                        "`controlled_wires` must have the same size as "
                        "`controlled_values`.");
        const auto iter =
            controlled_gate_kernels_.find(std::make_pair(op_name, kernel));
        PL_ABORT_IF(
            iter == controlled_gate_kernels_.cend(),
            "Cannot find a registered kernel for a given controlled gate "
            "and kernel pair");
        (iter->second)(data, num_qubits, controlled_wires, controlled_values,
                       wires, inverse, params);
    }

    void
    applyOperations(KernelType kernel, CFP_t *data, std::size_t num_qubits,
                    const std::vector<std::string> &ops,
                    const std::vector<std::vector<std::size_t>> &wires,
                    const std::vector<bool> &inverse,
                    const std::vector<std::vector<PrecisionT>> &params) const {
        const std::size_t numOperations = ops.size();
        PL_ABORT_IF(numOperations != wires.size() ||
                        numOperations != params.size(),
                    "Invalid arguments: number of operations, wires, and "
                    "parameters must all be equal");
        for (size_t i = 0; i < numOperations; i++) {
            applyOperation(kernel, data, num_qubits, ops[i], wires[i],
                           inverse[i], params[i]);
        }
    }

    void applyOperations(KernelType kernel, CFP_t *data, std::size_t num_qubits,
                         const std::vector<std::string> &ops,
                         const std::vector<std::vector<std::size_t>> &wires,
                         const std::vector<bool> &inverse) const {
        const std::size_t numOperations = ops.size();
        PL_ABORT_IF(numOperations != wires.size(),
                    "Invalid arguments: number of operations, wires, and "
                    "parameters must all be equal");
        for (size_t i = 0; i < numOperations; i++) {
            applyOperation(kernel, data, num_qubits, ops[i], wires[i],
                           inverse[i], {});
        }
    }

    void applyMatrix(KernelType kernel, CFP_t *data, std::size_t num_qubits,
                     const std::complex<PrecisionT> *matrix,
                     const std::vector<std::size_t> &wires,
                     bool inverse) const {
        PL_ASSERT(num_qubits >= wires.size());

        const auto mat_op = [n_wires = wires.size()]() {
            switch (n_wires) {
            case 1:
                return MatrixOperation::SingleQubitOp;
            case 2:
                return MatrixOperation::TwoQubitOp;
            default:
                return MatrixOperation::MultiQubitOp;
            }
        }();

        const auto iter = matrix_kernels_.find(std::make_pair(mat_op, kernel));
        PL_ABORT_IF(iter == matrix_kernels_.end(),
                    std::string(lookup(GateConstant::matrix_names, mat_op)) +
                        " is not registered for the given kernel");
        (iter->second)(data, num_qubits, matrix, wires, inverse);
    }

    void applyMatrix(KernelType kernel, CFP_t *data, std::size_t num_qubits,
                     const std::vector<std::complex<PrecisionT>> &matrix,
                     const std::vector<std::size_t> &wires,
                     bool inverse) const {
        PL_ABORT_IF_NOT(matrix.size() == exp2(2 * wires.size()),
                        "The size of matrix does not match with the given "
                        "number of wires");
        applyMatrix(kernel, data, num_qubits, matrix.data(), wires, inverse);
    }

    void applyControlledMatrix(KernelType kernel, CFP_t *data,
                               std::size_t num_qubits,
                               const std::complex<PrecisionT> *matrix,
                               const std::vector<std::size_t> &controlled_wires,
                               const std::vector<bool> &controlled_values,
                               const std::vector<std::size_t> &wires,
                               bool inverse) const {
        PL_ASSERT(num_qubits >= controlled_wires.size() + wires.size());
        PL_ABORT_IF_NOT(controlled_wires.size() == controlled_values.size(),
                        "`controlled_wires` must have the same size as "
                        "`controlled_values`.");
        const auto mat_op = [n_wires = wires.size()]() {
            switch (n_wires) {
            case 1:
                return ControlledMatrixOperation::NCSingleQubitOp;
            case 2:
                return ControlledMatrixOperation::NCTwoQubitOp;
            default:
                return ControlledMatrixOperation::NCMultiQubitOp;
            }
        }();

        const auto iter =
            controlled_matrix_kernels_.find(std::make_pair(mat_op, kernel));
        PL_ABORT_IF(
            iter == controlled_matrix_kernels_.end(),
            std::string(lookup(GateConstant::controlled_matrix_names, mat_op)) +
                " is not registered for the given kernel");
        (iter->second)(data, num_qubits, matrix, controlled_wires,
                       controlled_values, wires, inverse);
    }

    auto applyGenerator(KernelType kernel, CFP_t *data, std::size_t num_qubits,
                        GeneratorOperation gntr_op,
                        const std::vector<std::size_t> &wires, bool adj) const
        -> PrecisionT {
        using Pennylane::Gates::Constant::generator_names;
        const auto iter =
            generator_kernels_.find(std::make_pair(gntr_op, kernel));
        PL_ABORT_IF(iter == generator_kernels_.cend(),
                    "Cannot find a registered kernel for a given generator "
                    "and kernel pair.");
        return (iter->second)(data, num_qubits, wires, adj);
    }

    auto applyGenerator(KernelType kernel, CFP_t *data, std::size_t num_qubits,
                        const std::string &op_name,
                        const std::vector<std::size_t> &wires, bool adj) const
        -> PrecisionT {
        return applyGenerator(kernel, data, num_qubits,
                              strToGeneratorOp(op_name), wires, adj);
    }

    auto
    applyControlledGenerator(KernelType kernel, CFP_t *data,
                             std::size_t num_qubits,
                             const ControlledGeneratorOperation gntr_op,
                             const std::vector<std::size_t> &controlled_wires,
                             const std::vector<bool> &controlled_values,
                             const std::vector<std::size_t> &wires,
                             bool inverse) const -> PrecisionT {

        using Pennylane::Gates::Constant::controlled_generator_names;
        const auto iter =
            controlled_generator_kernels_.find(std::make_pair(gntr_op, kernel));
        PL_ABORT_IF(iter == controlled_generator_kernels_.cend(),
                    "Cannot find a registered kernel for a given controlled "
                    "generator "
                    "and kernel pair.");
        return (iter->second)(data, num_qubits, controlled_wires,
                              controlled_values, wires, inverse);
    }

    auto
    applyControlledGenerator(KernelType kernel, CFP_t *data,
                             std::size_t num_qubits, const std::string &op_name,
                             const std::vector<std::size_t> &controlled_wires,
                             const std::vector<bool> &controlled_values,
                             const std::vector<std::size_t> &wires,
                             bool inverse) const -> PrecisionT {
        return applyControlledGenerator(
            kernel, data, num_qubits, strToControlledGeneratorOp(op_name),
            controlled_wires, controlled_values, wires, inverse);
    }
};
} // namespace Pennylane::LightningQubit

namespace Pennylane::LightningQubit::Internal {
int registerAllAvailableKernels_Float();
int registerAllAvailableKernels_Double();

struct RegisterBeforeMain_Float {
    const static inline int dummy = registerAllAvailableKernels_Float();
};

struct RegisterBeforeMain_Double {
    const static inline int dummy = registerAllAvailableKernels_Double();
};
} // namespace Pennylane::LightningQubit::Internal