#include <gtest/gtest.h>

#include <vector>

#include <eigen3/Eigen/Sparse>

#include "base/consts.h"
#include "base/datatypes.h"
#include "tbpm/csr.h"
#include "tbpm/dense.h"
#include "tbpm/utils.h"

namespace {
using tbplas::base::complex_t;
using tbplas::base::INFINITESIMAL;
using tbplas::tbpm::CSRMatrixSquare;
using tbplas::tbpm::RandomGenerator;

template <typename t1, typename t2>
void assert_matrix_eq(const t1& a, const t2& b, double scale = 1.0)
{
    t1 delta = a - b;
    ASSERT_LE(delta.array().abs().maxCoeff(), scale * INFINITESIMAL);
}

template <typename t1, typename t2>
void assert_matrix_no_eq(const t1& a, const t2& b, double scale = 1.0)
{
    t1 delta = a - b;
    ASSERT_GE(delta.array().abs().maxCoeff(), scale * INFINITESIMAL);
}

TEST(RandomGenerator, randomnumber)
{
   RandomGenerator rand1, rand2;

   // Check random numbers from default seed
   for (int i = 0; i < 100; ++i) {
       ASSERT_DOUBLE_EQ(rand1.random_number(), rand2.random_number());
   }

   // Check random numbers from given seed
   for (int i = 0; i < 100; ++i) {
       ASSERT_DOUBLE_EQ(rand1.random_number(i), rand2.random_number(i));
   }

   // Subsequentially random numbers should differ
   for (int i = 0; i < 100; ++i) {
       double r1 = rand1.random_number();
       double r2 = rand1.random_number();
       double diff = r1 - r2;
       ASSERT_GE(std::abs(diff), INFINITESIMAL);
   }

   // Random numbers from different seeds should differ
   for (int i = 0; i < 100; ++i) {
       double diff = rand1.random_number(i) - rand2.random_number(i + 1);
       ASSERT_GE(std::abs(diff), INFINITESIMAL);
   }
}

TEST(RandomGenerator, random_state)
{
   RandomGenerator rand1, rand2;
   Eigen::VectorXcd vec1(100), vec2(100);

   // Check random vectors from default seed
   for (int i = 0; i < 100; ++i) {
       rand1.random_state(vec1);
       rand2.random_state(vec2);
       assert_matrix_eq(vec1, vec2);
   }

   // Check random vectors from given seed
   for (int i = 0; i < 100; ++i) {
       rand1.random_state(vec1, i);
       rand2.random_state(vec2, i);
       assert_matrix_eq(vec1, vec2);
   }

   // Subsequentially random vectors should differ
   for (int i = 0; i < 100; ++i) {
       rand1.random_state(vec1);
       rand1.random_state(vec2);
       assert_matrix_no_eq(vec1, vec2);
   }

   // Random vectors from different seeds should differ
   for (int i = 0; i < 100; ++i) {
       rand1.random_state(vec1, i);
       rand2.random_state(vec2, i + 1);
       assert_matrix_no_eq(vec1, vec2);
   }

   // Should fail on empty vectors
   Eigen::VectorXcd vec3;
   ASSERT_DEATH({ rand1.random_state(vec3); }, "Assertion");
}

TEST(CSRMatrixSquare, all)
{
    //double
    // Create reference matrix using Eigen sparse matrix
    std::vector<Eigen::Triplet<double>> triplet;
    triplet.emplace_back(0, 1, 2.0);
    triplet.emplace_back(0, 2, 1.0);
    triplet.emplace_back(0, 3, 7.0);
    triplet.emplace_back(1, 1, 3.0);
    triplet.emplace_back(1, 3, 2.0);
    triplet.emplace_back(2, 2, 1.0);
    triplet.emplace_back(3, 0, 3.0);
    triplet.emplace_back(3, 3, 2.0);
    Eigen::SparseMatrix<double, Eigen::RowMajor> mat(4, 4);
    mat.setFromTriplets(triplet.begin(), triplet.end());

    // Create user-defined csr matrix
    std::vector<int> indptr = { 0, 3, 5, 6, 8 };
    std::vector<int> indices = { 1, 2, 3, 1, 3, 2, 0, 3 };
    Eigen::VectorXd values { { 2.0 }, { 1.0 }, { 7.0 }, { 3.0 }, { 2.0 }, { 1.0 }, { 3.0 }, { 2.0 } };
    CSRMatrixSquare<int, Eigen::VectorXd> mat2(indptr, indices, values);

    // Create working vectors
    Eigen::Vector4d vec_in(-1.0, 2.0, 1.0, -0.5);
    Eigen::Vector4d vec_in2(1.2, -2.0, -0.5, 1.8);
    Eigen::Vector4d vec_out1;
    Eigen::Vector4d vec_out2;

    // Test mv
    vec_out1 = mat * vec_in;
    mat2.mv(vec_in, vec_out2);
    assert_matrix_eq(vec_out1, vec_out2);

    // Test amv
    vec_out1 = 2.0 * mat * vec_in;
    mat2.amv(2.0, vec_in, vec_out2);
    assert_matrix_eq(vec_out1, vec_out2);

    // Test amxsy
    vec_out1 = 2.0 * mat * vec_in - vec_out1;
    mat2.amxsy(2.0, vec_in, vec_out2);

    // Test amxsy_out
    vec_out1 = 2.0 * mat * vec_in - vec_in2;
    mat2.amxsy_out(2.0, vec_in, vec_in2, vec_out2);
    assert_matrix_eq(vec_out1, vec_out2);

    // Test xmy
    complex_t r1 = vec_in.dot(mat * vec_in2);
    complex_t r2 = mat2.xmy(vec_in, vec_in2);
    ASSERT_FLOAT_EQ(r1.real(), r2.real());
    ASSERT_FLOAT_EQ(r1.imag(), r2.imag());

    ////complex
    // Create reference matrix using Eigen sparse matrix
    std::vector<Eigen::Triplet<std::complex<double>>> triplet_;
    triplet_.emplace_back(0, 1, 2.0);
    triplet_.emplace_back(0, 2, 1.0);
    triplet_.emplace_back(0, 3, 7.0);
    triplet_.emplace_back(1, 1, 3.0);
    triplet_.emplace_back(1, 3, 2.0);
    triplet_.emplace_back(2, 2, 1.0);
    triplet_.emplace_back(3, 0, 3.0);
    triplet_.emplace_back(3, 3, 2.0);
    Eigen::SparseMatrix<std::complex<double>, Eigen::RowMajor> mat_(4, 4);
    mat_.setFromTriplets(triplet_.begin(), triplet_.end());

    // Create user-defined csr matrix
    std::vector<int> indptr_ = { 0, 3, 5, 6, 8 };
    std::vector<int> indices_ = { 1, 2, 3, 1, 3, 2, 0, 3 };
    Eigen::VectorXcd values_{ { 2.0 }, { 1.0 }, { 7.0 }, { 3.0 }, { 2.0 }, { 1.0 }, { 3.0 }, { 2.0 } };
    CSRMatrixSquare<int, Eigen::VectorXcd> mat2_(indptr_, indices_, values_);
    mat2_.print();

    // Create working vectors
    Eigen::Vector4cd vec_in_(-1.0, 2.0, 1.0, -0.5);
    Eigen::Vector4cd vec_in2_(1.2, -2.0, -0.5, 1.8);
    Eigen::Vector4cd vec_out1_;
    Eigen::Vector4cd vec_out2_;

    //// Test mv
    vec_out1_ = mat_ * vec_in_;
    mat2_.mv(vec_in_, vec_out2_);
    assert_matrix_eq(vec_out1_, vec_out2_);

    //// Test amv
    vec_out1_ = 2.0 * mat_ * vec_in_;
    mat2_.amv(2.0, vec_in_, vec_out2_);
    assert_matrix_eq(vec_out1_, vec_out2_);

    // Test amxsy
    vec_out1_ = 2.0 * mat_ * vec_in_ - vec_out1_;
    mat2_.amxsy(2.0, vec_in_, vec_out2_);

    // Test amxsy_out
    vec_out1_ = 2.0 * mat_ * vec_in_ - vec_in2_;
    mat2_.amxsy_out(2.0, vec_in_, vec_in2_, vec_out2_);
    assert_matrix_eq(vec_out1_, vec_out2_);

    // Test xmy
    complex_t r1_ = vec_in_.dot(mat_ * vec_in2_);
    complex_t r2_ = mat2_.xmy(vec_in_, vec_in2_);
    ASSERT_FLOAT_EQ(r1_.real(), r2_.real());
    ASSERT_FLOAT_EQ(r1_.imag(), r2_.imag());


    // Should fail on vectors with wrong size
    Eigen::Vector3d vec_err1;
    ASSERT_DEATH({ mat2.mv(vec_err1, vec_out1); }, "Assertion");
    ASSERT_DEATH({ mat2.mv(vec_out1, vec_err1); }, "Assertion");
    ASSERT_DEATH({ mat2.amv(1.0, vec_err1, vec_out1); }, "Assertion");
    ASSERT_DEATH({ mat2.amv(1.0, vec_out1, vec_err1); }, "Assertion");
    ASSERT_DEATH({ mat2.amxsy(1.0, vec_err1, vec_out1); }, "Assertion");
    ASSERT_DEATH({ mat2.amxsy(1.0, vec_out1, vec_err1); }, "Assertion");
    ASSERT_DEATH({ mat2.amxsy_out(1.0, vec_err1, vec_in, vec_out1); }, "Assertion");
    ASSERT_DEATH({ mat2.amxsy_out(1.0, vec_in, vec_err1, vec_out1); }, "Assertion");
    ASSERT_DEATH({ mat2.amxsy_out(1.0, vec_in, vec_in2, vec_err1); }, "Assertion");
    ASSERT_DEATH({ mat2.xmy(vec_err1, vec_out1); }, "Assertion");
    ASSERT_DEATH({ mat2.xmy(vec_in, vec_err1); }, "Assertion");
}

TEST(dense, normal)
{
    RandomGenerator rand;
    Eigen::VectorXcd vec1(100);
    Eigen::VectorXcd vec2(100);
    Eigen::VectorXcd vec3(100);
    Eigen::VectorXcd result1(100);
    Eigen::VectorXcd result2(100);
    Eigen::VectorXcd result_err(10);
    for (int i = 0; i < 100; ++i) {
        rand.random_state(vec1);
        rand.random_state(vec2);
        rand.random_state(vec3);

        /**
         * @brief vector-vector
         *
         */
        // vec_add_vec
        result1 = vec1 + vec2;
        tbplas::tbpm::vec_add_vec(vec1, vec2, result2);
        assert_matrix_eq(result1, result2, 20);

        // vec_sub_vec
        result1 = vec1 - vec2;
        tbplas::tbpm::vec_sub_vec(vec1, vec2, result2);
        assert_matrix_eq(result1, result2, 20);

        // vec_mul_vec
        result1 = vec1.array() * vec2.array();
        tbplas::tbpm::vec_mul_vec(vec1, vec2, result2);
        assert_matrix_eq(result1, result2, 20);

        // vec_div_vec
        result1 = vec1.array() / vec2.array();
        tbplas::tbpm::vec_div_vec(vec1, vec2, result2);
        //3e-14
        assert_matrix_eq(result1, result2, 200);

        /**
         * @brief vector-scalar
         *
         */
        // vec_add_sca
        result1 = vec1.array() + 0.5;
        tbplas::tbpm::vec_add_sca(vec1, 0.5, result2);
        assert_matrix_eq(result1, result2, 20);

        // vec_sub_sca
        result1 = vec1.array() - 0.5;
        tbplas::tbpm::vec_sub_sca(vec1, 0.5, result2);
        assert_matrix_eq(result1, result2, 20);

        // vec_mul_sca
        result1 = vec1 * -0.5;
        tbplas::tbpm::vec_mul_sca(vec1, -0.5, result2);
        assert_matrix_eq(result1, result2, 20);

        // vec_div_sca
        result1 = vec1 / -0.5;
        tbplas::tbpm::vec_div_sca(vec1, -0.5, result2);
        assert_matrix_eq(result1, result2, 20);

        /**
         * @brief vector-self-vector
         *
         */
        // vec_self_add_vec
        result1 += vec1;
        tbplas::tbpm::vec_self_add_vec(result2, vec1);
        assert_matrix_eq(result1, result2, 20);

        // vec_self_sub_vec
        result1 -= vec1;
        tbplas::tbpm::vec_self_sub_vec(result2, vec1);
        assert_matrix_eq(result1, result2, 20);

        // vec_self_mul_vec
        result1.array() *= vec1.array();
        tbplas::tbpm::vec_self_mul_vec(result2, vec1);
        assert_matrix_eq(result1, result2, 20);

        // vec_self_div_vec
        result1.array() /= vec1.array();
        tbplas::tbpm::vec_self_div_vec(result2, vec1);
        assert_matrix_eq(result1, result2, 20);

        /**
         * @brief vector-self-scalar
         *
         */
        // vec_self_add_sca
        result1.array() += 0.18;
        tbplas::tbpm::vec_self_add_sca(result2, 0.18);
        assert_matrix_eq(result1, result2, 20);

        // vec_self_sub_sca
        result1.array() -= 0.18;
        tbplas::tbpm::vec_self_sub_sca(result2, 0.18);
        assert_matrix_eq(result1, result2, 20);

        // vec_self_mul_sca
        result1 *= 0.18;
        tbplas::tbpm::vec_self_mul_sca(result2, 0.18);
        assert_matrix_eq(result1, result2, 20);

        // vec_self_div_sca
        result1 /= -0.25;
        tbplas::tbpm::vec_self_div_sca(result2, -0.25);
        assert_matrix_eq(result1, result2, 20);

        /**
         * @brief vec_dot
         *
         */
        // vec_dot for complex vectors
        complex_t prod_ref = vec1.dot(vec2);
        complex_t prod_test = tbplas::tbpm::vec_dot(vec1, vec2);
        complex_t diff = prod_ref - prod_test;
        ASSERT_LE(diff.real(), INFINITESIMAL);
        ASSERT_LE(diff.imag(), INFINITESIMAL);

        // vec_dot for real vectors
        Eigen::VectorXd vec1_real = vec1.real();
        Eigen::VectorXd vec2_real = vec2.real();
        double prod_ref_real = vec1_real.dot(vec2_real);
        double prod_test_real = tbplas::tbpm::vec_dot(vec1_real, vec2_real);
        double diff_real = prod_ref_real - prod_test_real;
        ASSERT_LE(diff_real, INFINITESIMAL);

        // vec_dot_no_conj for complex vectors
        prod_ref = vec1.conjugate().dot(vec2);
        prod_test = tbplas::tbpm::vec_dot_no_conj(vec1, vec2);
        diff = prod_ref - prod_test;
        ASSERT_LE(diff.real(), INFINITESIMAL);
        ASSERT_LE(diff.imag(), INFINITESIMAL);



        /**
         * @brief vec_norm
         *
         */
        // vec_norm for complex vectors
        double norm_ref = vec1.norm();
        double norm_test = tbplas::tbpm::vec_norm(vec1);
        double norm_diff = norm_ref - norm_test;
        ASSERT_LE(norm_diff, INFINITESIMAL);

        // vec_norm for real vectors
        norm_ref = vec1_real.norm();
        norm_test = tbplas::tbpm::vec_norm(vec1_real);
        norm_diff = norm_ref - norm_test;
        ASSERT_LE(norm_diff, INFINITESIMAL);

         /**
         * @brief vec_norm2
         *
         */
         // vec_norm for complex vectors
        norm_ref = std::pow(vec1.norm(), 2);
        norm_test = tbplas::tbpm::vec_norm2(vec1);
        norm_diff = norm_ref - norm_test;
        ASSERT_LE(norm_diff, INFINITESIMAL);

        // vec_norm for real vectors
        norm_ref = std::pow(vec1_real.norm(), 2);
        norm_test = tbplas::tbpm::vec_norm2(vec1_real);
        norm_diff = norm_ref - norm_test;
        ASSERT_LE(norm_diff, INFINITESIMAL);

        /**
         * @brief vec_copy
         *
         */
        // vec_copy for complex vectors
        result1 = vec1;
        tbplas::tbpm::vec_copy(vec1, result2);
        assert_matrix_eq(result1, result2, 20);

        // vec_copy for real vectors
        Eigen::VectorXd result1_real(vec1.size());
        Eigen::VectorXd result2_real(vec1.size());
        result1_real = vec1_real;
        tbplas::tbpm::vec_copy(vec1_real, result2_real);
        assert_matrix_eq(result1, result2, 20);


        /**
         * @brief mat_init
         *
         */
        std::vector<Eigen::VectorXcd> mat;
        tbplas::tbpm::mat_alloc(mat, 100, 20);
        ASSERT_EQ(mat.size(), 20);
        ASSERT_EQ(mat[0].size(), 100);
        tbplas::tbpm::mat_init(mat, 1.0);
        Eigen::VectorXcd ref(100);
        ref.array() = 1.0;
        for (const Eigen::VectorXcd& v : mat) {
            assert_matrix_eq(v, ref);
        }

        /**
         * @brief mat_col_copy
         *
         */
        tbplas::tbpm::mat_col_copy(vec1, mat);
        assert_matrix_eq(mat[0], vec1);
        assert_matrix_eq(mat[19], vec1);

        /**
         * @brief composite operations
         *
         */
        // axpy
        result1 = 1.5 * vec1 + result1;
        tbplas::tbpm::axpy(1.5, vec1, result2);
        assert_matrix_eq(result1, result2, 20);

        // axpbypz
        result1 = -0.1 * vec1 + 1.2 * vec2 + result1;
        tbplas::tbpm::axpbypz(-0.1, vec1, 1.2, vec2, result2);
        assert_matrix_eq(result1, result2, 20);

        // axpbypcz_out
        result1 = -0.1 * vec1 + 1.2 * vec2 + 0.5 * vec3;
        tbplas::tbpm::axpbypcz_out(-0.1, vec1, 1.2, vec2, 0.5, vec3, result2);
        assert_matrix_eq(result1, result2, 20);

        // xmy_diag
        complex_t r1 = tbplas::tbpm::xmy_diag(vec1, vec2, vec3);
        complex_t r2 = (vec1.array().conjugate() * vec2.array() * vec3.array()).sum();
        ASSERT_FLOAT_EQ(r1.real(), r2.real());
        ASSERT_FLOAT_EQ(r1.imag(), r2.imag());

        /**
         * @brief vec_init
         * @note This has to be done in the last since it resets all vectors.
         */
        // vec_init for complex vectors
        result1.array() = complex_t(-1.5, 2.0);
        tbplas::tbpm::vec_init(result2, complex_t(-1.5, 2.0));
        assert_matrix_eq(result1, result2, 20);

        // vec_init for real vectors
        result1_real.array() = 2.1;
        tbplas::tbpm::vec_init(result2_real, 2.1);
        assert_matrix_eq(result1_real, result2_real, 20);

        /**
         * @brief vec_trunc
         *
         */
        int dim = 100;
        Eigen::VectorXd v1(dim);
        for (int i = 0; i < dim; ++i) {
            v1[i] = dim - i;
        }
        Eigen::VectorXd v2 = v1;
        Eigen::VectorXd v3 = v1;

        // If all elements smaller than eps, keep only the first element
        tbplas::tbpm::vec_trunc(v1, 1000.0);
        ASSERT_EQ(v1.size(), 1);

        // Some elements smaller than eps, remove that elements
        tbplas::tbpm::vec_trunc(v2, 5.5);
        ASSERT_EQ(v2.size(), 96);

        // All elements larger than eps, keep as it is
        tbplas::tbpm::vec_trunc(v3, 0.1);
        ASSERT_EQ(v3.size(), 100);
    }
}

TEST(dense, assert)
{
   RandomGenerator rand;
   Eigen::VectorXcd vec1(100);
   Eigen::VectorXcd vec2(100);
   Eigen::VectorXcd vec3(100);
   Eigen::VectorXcd vec_error(10);
   Eigen::VectorXcd result(100);
   rand.random_state(vec1);
   rand.random_state(vec2);
   rand.random_state(vec3);

   /**
    * @brief vector-vector
    *
    */
   // vec_add_vec
   ASSERT_DEATH({ tbplas::tbpm::vec_add_vec(vec_error, vec1, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_add_vec(vec1, vec_error, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_add_vec(vec1, vec2, vec_error); }, "Assertion");

   // vec_sub_vec
   ASSERT_DEATH({ tbplas::tbpm::vec_sub_vec(vec_error, vec1, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_sub_vec(vec1, vec_error, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_sub_vec(vec1, vec2, vec_error); }, "Assertion");

   // vec_mul_vec
   ASSERT_DEATH({ tbplas::tbpm::vec_mul_vec(vec_error, vec1, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_mul_vec(vec1, vec_error, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_mul_vec(vec1, vec2, vec_error); }, "Assertion");

   // vec_div_vec
   ASSERT_DEATH({ tbplas::tbpm::vec_div_vec(vec_error, vec1, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_div_vec(vec1, vec_error, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_div_vec(vec1, vec2, vec_error); }, "Assertion");

   /**
    * @brief vector-self-vector
    *
    */
   // vec_self_add_vec
   ASSERT_DEATH({ tbplas::tbpm::vec_self_add_vec(vec_error, vec1); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_self_add_vec(vec1, vec_error); }, "Assertion");

   // vec_self_sub_vec
   ASSERT_DEATH({ tbplas::tbpm::vec_self_sub_vec(vec_error, vec1); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_self_sub_vec(vec1, vec_error); }, "Assertion");

   // vec_self_mul_vec
   ASSERT_DEATH({ tbplas::tbpm::vec_self_mul_vec(vec_error, vec1); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_self_mul_vec(vec1, vec_error); }, "Assertion");

   // vec_self_div_vec
   ASSERT_DEATH({ tbplas::tbpm::vec_self_div_vec(vec_error, vec1); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_self_div_vec(vec1, vec_error); }, "Assertion");

   // vec_dot
   Eigen::VectorXd vec1_real = vec1.real();
   Eigen::VectorXd error_real = vec_error.real();
   ASSERT_DEATH({ tbplas::tbpm::vec_dot(vec_error, vec1); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_dot(vec1, vec_error); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_dot(error_real, vec1_real); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_dot(vec1_real, error_real); }, "Assertion");

   // vec_dot_no_conj
    ASSERT_DEATH({ tbplas::tbpm::vec_dot_no_conj(vec_error, vec1); }, "Assertion");
    ASSERT_DEATH({ tbplas::tbpm::vec_dot_no_conj(vec1, vec_error); }, "Assertion");
    ASSERT_DEATH({ tbplas::tbpm::vec_dot_no_conj(error_real, vec1_real); }, "Assertion");
    ASSERT_DEATH({ tbplas::tbpm::vec_dot_no_conj(vec1_real, error_real); }, "Assertion");

   // vec_copy
   ASSERT_DEATH({ tbplas::tbpm::vec_copy(vec1, vec_error); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::vec_copy(vec_error, vec1); }, "Assertion");

   // vec_trunc
   Eigen::VectorXd temp;
   ASSERT_DEATH({ tbplas::tbpm::vec_trunc(temp, 0.1); }, "Assertion");

   // mat_col_copy
   std::vector<Eigen::VectorXcd> mat;
   tbplas::tbpm::mat_alloc(mat, 20, 20);
   ASSERT_DEATH({ tbplas::tbpm::mat_col_copy(vec1, mat); }, "Assertion");

   // axpy
   ASSERT_DEATH({ tbplas::tbpm::axpy(1.5, vec_error, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::axpy(1.5, vec1, vec_error); }, "Assertion");

   // axpbypz
   ASSERT_DEATH({ tbplas::tbpm::axpbypz(-0.1, vec_error, 1.2, vec2, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::axpbypz(-0.1, vec1, 1.2, vec_error, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::axpbypz(-0.1, vec1, 1.2, vec2, vec_error); }, "Assertion");

   // axpbypcz_out
   ASSERT_DEATH({ tbplas::tbpm::axpbypcz_out(-0.1, vec_error, 1.2, vec2, 1.5, vec3, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::axpbypcz_out(-0.1, vec1, 1.2, vec_error, 1.5, vec3, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::axpbypcz_out(-0.1, vec1, 1.2, vec2, 1.5, vec_error, result); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::axpbypcz_out(-0.1, vec1, 1.2, vec2, 1.5, vec3, vec_error); }, "Assertion");

   // xmy_diag
   ASSERT_DEATH({ tbplas::tbpm::xmy_diag(vec1, vec_error, vec2); }, "Assertion");
   ASSERT_DEATH({ tbplas::tbpm::xmy_diag(vec1, vec2, vec_error); }, "Assertion");
}
} // namespace
