/**
 * @brief Functions for performing TBPM calculations
 *
 * @author: Zewen Wu, Miao Zhang, Yunhai Li
 */
#ifdef WITH_CUDA
#ifndef TBPLAS_TBPM_TBPM_GPU_H
#define TBPLAS_TBPM_TBPM_GPU_H

#include <iostream>
#include <vector>

#include <eigen3/Eigen/Dense>

#include "backend.h"
#include "base/utils.h"
#include "config.h"
#include "cuda/cudaConfig.h"
#include "dense.h"
#include "fermi_dirac.h"
#include "propagation.h"
#include "utils.h"

namespace tbplas::tbpm {
using tbplas::base::ProgressBar;

template <typename sparse_t>
class TBPMGPU final : public AbstractTBPM<sparse_t> {
private:
    CudaConfig cuda_;

    /**
     * @brief Shared function for calculating quasi-eigenstates (and corr_dos) from initial state
     *
     * @param[in] propagator propagator
     * @param[in] h_sparse sparse Hamiltonian
     * @param[in] config user-defined calculation configurations
     * @param[in] meta additional meta info.
     * @param[in] calc_corr_dos whether to calculate DOS correlation function
     * @param[in] wf0 initial wave function
     * @param[out] wfq quasi-eigenstates from initial state, dimension (num_eng, num_wf),
     *   MUST BE ALLOCATED in the caller
     * @param[out] corr_dos correlation function of dos, dimension (num_step),
     *   MUST be ALLOCATED in the caller if calc_corr_dos == true
     *
     * @note
     *   1. propagator.act() changes its working arrays, so it is & rather than const &.
     *   2. DO NOT use auto for eigen datatypes (vectors in wfq).
     */
    void tbpm_qe_dos(
        Propagator<sparse_t>& propagator,
        const sparse_t& h_sparse,
        const TBPMConfig& config,
        const TBPMMeta& meta,
        const bool& calc_corr_dos,
        const Eigen::VectorXcd& wf0,
        std::vector<Eigen::VectorXcd>& wfq,
        Eigen::VectorXcd& corr_dos)
    {
        // Initialize wave functions
        size_t n_wf = wf0.size();
        Eigen::VectorXcd wf_t_pos(n_wf);
        Eigen::VectorXcd wf_t_neg(n_wf);
        vec_copy(wf0, wf_t_pos);
        vec_copy(wf0, wf_t_neg);

        // Initialize quasi-eigenstates (and corr_dos)
        int num_steps = config.num_time_steps;
        size_t num_energies = meta.qe_energies.size();
        mat_col_copy(wf0, wfq);
        if (calc_corr_dos) {
            corr_dos.setZero();
        }

        // Propagation
        double norm_ref = vec_norm(wf0);
        auto h_meta = h_sparse.get_meta();
        auto prop_meta = propagator.get_meta();
        cuda_.tbpm_qe_dos_GPU(
            calc_corr_dos, num_steps,
            meta.time_step, meta.qe_energies.data(), num_energies,
            config.wfn_check_time_steps, config.wfn_check_threshold,
            wf0.data(), wf0.size(),
            wf_t_pos.data(), wf_t_pos.size(),
            wf_t_neg.data(), wf_t_neg.size(),
            norm_ref,
            prop_meta.bes_forward_data, prop_meta.bes_backward_data, prop_meta.bes_forward_size,
            prop_meta.t0_data, prop_meta.t0_size,
            prop_meta.t1_data, prop_meta.t1_size,
            h_meta.indptr_data, h_meta.indptr_size,
            h_meta.indices_data, h_meta.indices_size,
            h_meta.values_data, h_meta.values_size,
            h_meta.dim,
            corr_dos.data(), corr_dos.size(),
            wfq);

        // Normalization
        for (Eigen::VectorXcd& v : wfq) { // DO NOT use auto for eigen types!
            double norm = vec_norm(v);
            vec_self_div_sca(v, norm);
        }
    }

public:
    /**
     * @brief Calculate correlation function for DOS and LDOS
     *
     * @param[in] h_sparse scaled sparse Hamiltonian
     * @param[in] config user-defined calculation configurations
     * @param[in] meta additional meta info.
     * @param[out] corr correlation function of (L)DOS
     */
    void tbpm_dos(
        const sparse_t& h_sparse,
        const TBPMConfig& config,
        const TBPMMeta& meta,
        Eigen::VectorXcd& corr) final
    {
        if (config.ldos) {
            assert(!config.ldos_orbital_indices.empty());
            size_t num_orb = h_sparse.get_dim();
            for (const auto& i : config.ldos_orbital_indices) {
                assert((num_orb > i) && (i >= 0));
            }
            PARA_EXEC(std::cout << "Calculating LDOS correlation function." << std::endl);
        } else {
            PARA_EXEC(std::cout << "Calculating DOS correlation function." << std::endl);
        }

        // Initialize wave functions
        size_t n_wf = h_sparse.get_dim();
        Eigen::VectorXcd wf0(n_wf);
        Eigen::VectorXcd wf_t(n_wf);
        wf_t.resize(n_wf);

        // Initialize correlation function
        int num_steps = config.num_time_steps;
        corr = Eigen::VectorXcd::Zero(num_steps + 1);

        // Initialize random generator, propagator and progress bar
        RandomGenerator rand_gen(config.seed);
        Propagator<sparse_t> propagator(h_sparse, config, meta);
        ProgressBar prog_bar(num_steps, 64, "Finished timestep");

        // Loop over samples
        int num_samples = config.num_random_samples;
        int rank = meta.mpi_rank;

        for (int i_sample = 1; i_sample <= num_samples; ++i_sample) {
            PARA_EXEC(std::cout << "Sample " << i_sample << " of " << num_samples << std::endl);
            int seed_i = config.seed * (i_sample + rank * num_samples);

            // Randomize wf0 and copy to wf_t
            if (config.ldos) {
                // Generate a random vector for selected orbitals only
                Eigen::VectorXcd wf_proj(config.ldos_orbital_indices.size());
                rand_gen.random_state(wf_proj, seed_i);

                // Assign wf_proj to wf0
                vec_init(wf0, 0.0);
                size_t counter = 0;
                for (const size_t& i_orb : config.ldos_orbital_indices) {
                    wf0(i_orb) = wf_proj(counter);
                    ++counter;
                }
            } else {
                rand_gen.random_state(wf0, seed_i);
            }
            vec_copy(wf0, wf_t);

            // Update correlation function
            corr[0] += 1.0; // At t = 0 wf_t == wf0, so their inner product is always 1.
            complex_t norm_ref = 1.0; // As wf_t has been normalized for both DOS and LDOS, its norm is always 1.

            // Propagate
            auto h_meta = h_sparse.get_meta();
            auto prop_meta = propagator.get_meta();
            cuda_.tbpm_dos_GPU(
                wf_t.data(), wf_t.size(),
                num_steps, config.num_time_steps, config.wfn_check_threshold,
                wf0.data(), wf0.size(),
                norm_ref,
                prop_meta.bes_forward_data,
                prop_meta.bes_forward_size,
                prop_meta.t0_data, prop_meta.t0_size,
                prop_meta.t1_data, prop_meta.t1_size,
                prop_meta.wf_out_data, prop_meta.wf_out_size,
                h_meta.indptr_data, h_meta.indptr_size,
                h_meta.indices_data, h_meta.indices_size,
                h_meta.values_data, h_meta.values_size,
                h_meta.dim,
                corr.data(), corr.size(), prog_bar);
        }

        // Average over samples
        corr /= static_cast<double>(num_samples);
    }

    /**
     * @brief Calculate correlation function of AC conductivity.
     *
     * @param[in] h_sparse scaled sparse Hamiltonian
     * @param[in] curr_x sparse current operator along x direction
     * @param[in] curr_y sparse current operator along y direction
     * @param[in] config user-defined calculation configurations
     * @param[in] meta additional meta info.
     * @param[out] corr correlation function of AC conductivity, dimension: (4, num_steps)
     */
    void tbpm_ac_cond(
        const sparse_t& h_sparse,
        const sparse_t& curr_x,
        const sparse_t& curr_y,
        const TBPMConfig& config,
        const TBPMMeta& meta,
        Eigen::MatrixXcd& corr) final
    {
        PARA_EXEC(std::cout << "Calculating AC conductivity correlation function." << std::endl);
        bool is_row_major = false;

        // Initialize wave functions
        size_t n_wf = h_sparse.get_dim();
        Eigen::VectorXcd wf0(n_wf);
        Eigen::VectorXcd psi1_x(n_wf);
        Eigen::VectorXcd psi1_y(n_wf);
        Eigen::VectorXcd psi2(n_wf);

        // Initialize correlation function
        int num_steps = config.num_time_steps;
        corr = Eigen::MatrixXcd::Zero(4, num_steps);
        if (corr.IsRowMajor) {
            is_row_major = true;
        }

        // Initialize operators
        // Current operators are bare sparse matrices as function arguments.
        Propagator<sparse_t> propagator(h_sparse, config, meta);
        FermiDiracOperator<sparse_t> fermi_dirac(h_sparse, config, meta);

        // Initialize utilities
        RandomGenerator rand_gen(config.seed);
        ProgressBar prog_bar(num_steps, 64, "Finished timestep");

        // Loop over samples
        int num_samples = config.num_random_samples;
        int rank = meta.mpi_rank;
        for (int i_sample = 1; i_sample <= num_samples; ++i_sample) {
            PARA_EXEC(std::cout << "Sample " << i_sample << " of " << num_samples << std::endl);
            int seed_i = config.seed * (i_sample + rank * num_samples);
            PARA_EXEC(prog_bar.reset());

            // Make random state and psi1, psi2
            // See eqn. 53 of CPC paper.
            rand_gen.random_state(wf0, seed_i);
            curr_x.mv(wf0, psi1_x); // j_beta|psi> for beta = x
            curr_y.mv(wf0, psi1_y); // j_beta|psi> for beta = y
            fermi_dirac.act(psi1_x, true); // [1-f(H)]j_beta|psi> for beta = x
            fermi_dirac.act(psi1_y, true); // [1-f(H)]j_beta|psi> for beta = y
            fermi_dirac.act_out(wf0, psi2, false); // f(H)|psi>

            // Get initial correlation function in all directions
            corr(0, 0) += curr_x.xmy(psi2, psi1_x); // <psi|f(H)j_alpha[1-f(H)]j_beta|psi> for xx
            corr(1, 0) += curr_y.xmy(psi2, psi1_x); // yx
            corr(2, 0) += curr_x.xmy(psi2, psi1_y); // xy
            corr(3, 0) += curr_y.xmy(psi2, psi1_y); // yy
            PARA_EXEC(prog_bar.count());

            // Iterate over time
            // Norm check is performed on psi2. It can also be done for psi1_x and psi1_y.
            double norm_ref = vec_norm(psi2);
            auto h_meta = h_sparse.get_meta();
            auto prop_meta = propagator.get_meta();
            auto curr_x_meta = curr_x.get_meta();
            auto curr_y_meta = curr_y.get_meta();
            cuda_.tbpm_ac_cond_GPU(
                num_steps, config.wfn_check_time_steps, config.wfn_check_threshold,
                norm_ref,
                prop_meta.bes_forward_data, prop_meta.bes_forward_size,
                h_meta.indptr_data, h_meta.indptr_size,
                h_meta.indices_data, h_meta.indices_size,
                h_meta.values_data, h_meta.values_size,
                curr_x_meta.indptr_data, curr_x_meta.indptr_size,
                curr_x_meta.indices_data, curr_x_meta.indices_size,
                curr_x_meta.values_data, curr_x_meta.values_size,
                curr_y_meta.indptr_data, curr_y_meta.indptr_size,
                curr_y_meta.indices_data, curr_y_meta.indices_size,
                curr_y_meta.values_data, curr_y_meta.values_size,
                h_meta.dim,
                psi2.data(), psi2.size(),
                psi1_x.data(), psi1_x.size(),
                psi1_y.data(), psi1_y.size(),
                prop_meta.t0_data, prop_meta.t0_size,
                prop_meta.t1_data, prop_meta.t1_size,
                corr.data(), corr.size(), is_row_major, prog_bar);
        }

        // Average over samples
        corr /= static_cast<double>(num_samples);
    }

    /**
     * @brief Calculate correlation functions of DC conductivity.
     *
     * @tparam sparse_t datatype of sparse matrices
     * @param[in] curr_x sparse current operator along x direction
     * @param[in] curr_y sparse current operator along y direction
     * @param[in] config user-defined calculation configurations
     * @param[in] meta additional meta info.
     * @param[out] corr_dos correlation function of DOS, dimension (num_steps_dos, 1)
     * @param[out] corr_dc_xx correlation function of xx component of DC conductivity,
     *   dimension (num_energies, num_steps_dc)
     * @param[out] corr_dc_yy correlation function of yy component of DC conductivity,
     *   dimension (num_energies, num_steps_dc)
     */
    void tbpm_dc_cond(
        const sparse_t& h_sparse,
        const sparse_t& curr_x,
        const sparse_t& curr_y,
        const TBPMConfig& config,
        const TBPMMeta& meta,
        Eigen::VectorXcd& corr_dos,
        Eigen::MatrixXcd& corr_dc_xx,
        Eigen::MatrixXcd& corr_dc_yy) final
    {
        PARA_EXEC(std::cout << "Calculating DC conductivity correlation function." << std::endl);

        // Initialize wave functions
        size_t n_wf = h_sparse.get_dim();
        Eigen::VectorXcd wf0(n_wf);
        Eigen::VectorXcd psi0_x(n_wf); // j_x|psi0> in eqn.62 of CPC paper
        Eigen::VectorXcd psi0_y(n_wf); // j_y|psi0>

        // Initialize quasi-eigenstates
        size_t num_energies = meta.qe_energies.size();
        std::vector<Eigen::VectorXcd> wf_qe;
        mat_alloc(wf_qe, n_wf, num_energies);

        // Initialize correlation functions
        int num_steps_dos = config.num_time_steps;
        int num_steps_dc = config.dc_cond_num_time_steps;
        Eigen::VectorXcd corr_dos_work(num_steps_dos);
        corr_dos = Eigen::VectorXcd::Zero(num_steps_dos);
        corr_dc_xx = Eigen::MatrixXcd::Zero(num_energies, num_steps_dc);
        corr_dc_yy = Eigen::MatrixXcd::Zero(num_energies, num_steps_dc);
        bool is_row_major = false;
        if (corr_dc_xx.IsRowMajor) {
            is_row_major = true;
        }

        // Initialize operators and utilities
        Propagator<sparse_t> propagator(h_sparse, config, meta);
        RandomGenerator rand_gen(config.seed);
        ProgressBar prog_bar(num_steps_dc, 64, "Finished DC timestep");

        // Main loop
        int num_samples = config.num_random_samples;
        int rank = meta.mpi_rank;
        for (int i_sample = 1; i_sample <= num_samples; ++i_sample) {
            PARA_EXEC(std::cout << "Sample " << i_sample << " of " << num_samples << std::endl);
            int seed_i = config.seed * (i_sample + rank * num_samples);
            PARA_EXEC(prog_bar.reset());

            // Get quasi-eigenstates and corr_dos for this initial condition
            rand_gen.random_state(wf0, seed_i);
            tbpm_qe_dos(propagator, h_sparse, config, meta, true, wf0,
                wf_qe, corr_dos_work);
            vec_self_add_vec(corr_dos, corr_dos_work);

            // Calculate |<psi0|psi(E)>|
            Eigen::VectorXd factor(num_energies);
            for (size_t i_e = 0; i_e < num_energies; ++i_e) {
                factor[i_e] = std::abs(vec_dot(wf0, wf_qe[i_e]));
            }
            PARA_EXEC(prog_bar.count());

            // Make psi0_x and psi0_y
            curr_x.mv(wf0, psi0_x);
            curr_y.mv(wf0, psi0_y);

            // Propagation
            auto h_meta = h_sparse.get_meta();
            auto prop_meta = propagator.get_meta();
            auto curr_x_meta = curr_x.get_meta();
            auto curr_y_meta = curr_y.get_meta();
            cuda_.tbpm_dc_cond_GPU(
                num_steps_dc,
                prop_meta.bes_forward_data, prop_meta.bes_forward_size,
                h_meta.indptr_data, h_meta.indptr_size,
                h_meta.indices_data, h_meta.indices_size,
                h_meta.values_data, h_meta.values_size,
                curr_x_meta.indptr_data, curr_x_meta.indptr_size,
                curr_x_meta.indices_data, curr_x_meta.indices_size,
                curr_x_meta.values_data, curr_x_meta.values_size,
                curr_y_meta.indptr_data, curr_y_meta.indptr_size,
                curr_y_meta.indices_data, curr_y_meta.indices_size,
                curr_y_meta.values_data, curr_y_meta.values_size,
                h_meta.dim,
                factor.data(), factor.size(),
                psi0_x.data(), psi0_x.size(),
                psi0_y.data(), psi0_y.size(),
                prop_meta.t0_data, prop_meta.t0_size,
                prop_meta.t1_data, prop_meta.t1_size,
                corr_dc_xx.data(), corr_dc_xx.size(),
                corr_dc_yy.data(), corr_dc_yy.size(),
                wf_qe,
                is_row_major, num_energies, prog_bar);
        }

        // Average over samples
        corr_dos /= static_cast<double>(num_samples);
        corr_dc_xx /= static_cast<double>(num_samples);
        corr_dc_yy /= static_cast<double>(num_samples);
    }

    /**
     * @brief Calculate correlation function at given q-point
     *
     * @param[in] h_sparse scaled sparse Hamiltonian
     * @param[in] den_coeff_q coefficients for density operator at q
     * @param[in] den_coeff_mq coefficients for density operator at -q
     * @param[in] config user-defined calculation configurations
     * @param[in] meta additional meta info.
     * @param[out] corr correlation function of dynamic polarizability, dimension: (num_step, 1)
     */
    void tbpm_dyn_pol(
        const sparse_t& h_sparse,
        const Eigen::VectorXcd& den_coeff_q,
        const Eigen::VectorXcd& den_coeff_mq,
        const TBPMConfig& config,
        const TBPMMeta& meta,
        Eigen::VectorXd& corr) final
    {
        PARA_EXEC(std::cout << "Calculating dynamic polarizability correlation function." << std::endl);

        // Initialize wave functions
        size_t n_wf = h_sparse.get_dim();
        Eigen::VectorXcd wf0(n_wf);
        Eigen::VectorXcd psi1(n_wf);
        Eigen::VectorXcd psi2(n_wf);

        // Initialize correlation function
        int num_steps = config.num_time_steps;
        corr = Eigen::VectorXd::Zero(num_steps);

        // Initialize operators
        // Current operators are bare sparse matrices as function arguments.
        Propagator<sparse_t> propagator(h_sparse, config, meta);
        FermiDiracOperator<sparse_t> fermi_dirac(h_sparse, config, meta);

        // Initialize utilities
        RandomGenerator rand_gen(config.seed);
        ProgressBar prog_bar(num_steps, 64, "Finished timestep");

        int num_samples = config.num_random_samples;
        int rank = meta.mpi_rank;
        for (int i_sample = 1; i_sample <= num_samples; ++i_sample) {
            PARA_EXEC(std::cout << "Sample " << i_sample << " of " << num_samples << std::endl);
            int seed_i = config.seed * (i_sample + rank * num_samples);
            PARA_EXEC(prog_bar.reset());

            // Make random state and psi1, psi2
            // See eqn.79-80 of CPC paper.
            rand_gen.random_state(wf0, seed_i);
            vec_mul_vec(den_coeff_mq, wf0, psi1); // psi1 = rho(-q) * psi0
            fermi_dirac.act(psi1, true); // psi1 = (1-nF) * psi1
            fermi_dirac.act_out(wf0, psi2, false); // psi2 = nF * psi0

            // Initial value of correlation function
            // See eqn.77 of CPC paper.
            corr[0] += xmy_diag(psi2, den_coeff_q, psi1).imag(); // corr = Im[<psi2|rho(q)|psi1>]
            PARA_EXEC(prog_bar.count());

            double norm_ref = vec_norm(psi2);
            auto h_meta = h_sparse.get_meta();
            auto prop_meta = propagator.get_meta();
            cuda_.tbpm_dyn_pol_GPU(
                num_steps, config.wfn_check_time_steps, config.wfn_check_threshold,
                norm_ref,
                prop_meta.bes_forward_data, prop_meta.bes_forward_size,
                h_meta.indptr_data, h_meta.indptr_size,
                h_meta.indices_data, h_meta.indices_size,
                h_meta.values_data, h_meta.values_size,
                h_meta.dim,
                psi1.data(), psi1.size(),
                psi2.data(), psi2.size(),
                den_coeff_q.data(), den_coeff_q.size(),
                prop_meta.t0_data, prop_meta.t0_size,
                prop_meta.t1_data, prop_meta.t1_size,
                corr.data(), corr.size());
        }

        // Average over samples
        corr /= static_cast<double>(num_samples);
    }

    /**
     * @brief Calculate squared modulus of quasi-eigenstates for given energies.
     *
     * @param[in] h_sparse scaled sparse Hamiltonian
     * @param[in] config user-defined calculation configurations
     * @param[in] meta additional meta info.
     * @param[out] wf2_qe squared modulus of quasi-eigenstates, dimension: (n_wf, num_eng)
     */
    void tbpm_quasi_eigenstates(
        const sparse_t& h_sparse,
        const TBPMConfig& config,
        const TBPMMeta& meta,
        Eigen::MatrixXd& wf2_qe) final
    {
        PARA_EXEC(std::cout << "Calculating quasi-eigenstates." << std::endl);

        // Initialize wave functions
        size_t n_wf = h_sparse.get_dim();
        Eigen::VectorXcd wf0(n_wf);

        // Initialize quasi-eigenstates
        size_t num_energies = meta.qe_energies.size();
        wf2_qe = Eigen::MatrixXd::Zero(n_wf, num_energies);
        std::vector<Eigen::VectorXcd> wfq;
        mat_alloc(wfq, n_wf, num_energies);
        Eigen::VectorXcd corr_dos; // just for filling arguments of tbpm_qe_core

        // Initialize operators
        Propagator<sparse_t> propagator(h_sparse, config, meta);

        // Initialize utilities
        RandomGenerator rand_gen(config.seed);

        int num_samples = config.num_random_samples;
        int rank = meta.mpi_rank;
        double avg_factor = 1.0 / static_cast<double>(num_samples);
        for (int i_sample = 1; i_sample <= num_samples; ++i_sample) {
            PARA_EXEC(std::cout << "Sample " << i_sample << " of " << num_samples << std::endl);
            int seed_i = config.seed * (i_sample + rank * num_samples);

            // Get quasi-eigenstates for this initial condition
            rand_gen.random_state(wf0, seed_i);
            tbpm_qe_dos(propagator, h_sparse, config, meta, false, wf0,
                wfq, corr_dos);

            // Evaluate abs2 of qe and average over samples
            for (size_t i_e = 0; i_e < num_energies; ++i_e) {
                wf2_qe.col(i_e) += wfq[i_e].cwiseAbs2() * avg_factor;
            }
        }
    }

    /**
     * @brief Calculate time-dependent wave function.
     *
     * @param[in] h_sparse scaled sparse Hamiltonian
     * @param[in] config user-defined calculation configurations
     * @param[in] meta additional meta info.
     * @param[out] wft time-dependent wave functions, dimension (n_wf, num_save)
     */
    void tbpm_wft(
        const sparse_t& h_sparse,
        const TBPMConfig& config,
        const TBPMMeta& meta,
        Eigen::MatrixXcd& wft) final
    {
        PARA_EXEC(std::cout << "Calculating time-dependent wave function." << std::endl);

        // Initialize wave functions
        size_t n_wf = h_sparse.get_dim();
        size_t num_save = config.wft_time_save.size();
        Eigen::VectorXcd wf0 = config.wft_wf0;
        vec_self_div_sca(wf0, vec_norm(wf0));
        wft = Eigen::MatrixXcd::Zero(n_wf, num_save);

        // Initialize propagator and progress bar
        int num_steps = config.num_time_steps;
        Propagator<sparse_t> propagator(h_sparse, config, meta);
        bool is_row_major = false;
        if (wft.IsRowMajor) {
            is_row_major = true;
        }

        // Check if we need to save the initial state
        size_t counter = 0;
        if (config.wft_time_save.find(0) != config.wft_time_save.end()) {
            wft.col(counter) = wf0;
            counter++;
        }

        // Propagate
        double norm_ref = vec_norm(wf0);
        auto h_meta = h_sparse.get_meta();
        auto prop_meta = propagator.get_meta();
        cuda_.tbpm_wft_GPU(
            num_steps,
            config.wfn_check_time_steps, config.wfn_check_threshold,
            config.wft_time_save, wf0.data(), wf0.size(), norm_ref,
            prop_meta.bes_forward_data, prop_meta.bes_forward_size,
            prop_meta.t0_data, prop_meta.t0_size,
            prop_meta.t1_data, prop_meta.t1_size,
            h_meta.indptr_data, h_meta.indptr_size,
            h_meta.indices_data, h_meta.indices_size,
            h_meta.values_data, h_meta.values_size,
            h_meta.dim,
            wft.data(), wft.size(), wft.cols(), is_row_major);
    }
};
} // namespace tbplas::tbpm

#endif
#endif