/**
    General definitions

    @author Álvaro Barbero Jiménez
    @author Suvrit Sra
*/
#ifndef _GENERAL_H
#define _GENERAL_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <float.h>
#include <limits.h>

/* Mex and LAPACK includes */
#ifdef NOMATLAB
//#undef lapack_int
//#define lapack_int              long
//#include <lapacke.h>
//#undef MKL_INT
//#define MKL_INT              long
#include <mkl.h>
inline double mxGetInf() { return INFINITY; }
#else
#include "mex.h"
#include "lapack.h"
#include "matrix.h"
#define lapack_int  ptrdiff_t
#endif

/* Uncomment to print debug messages to a debug file */
//#define DEBUG
/* Choose here the name of the debug file */
#ifdef DEBUG
    static FILE* DEBUG_FILE = fopen("debug.tmp","w");
#else
    static FILE* DEBUG_FILE = NULL;
#endif
#define DEBUG_N 10 /* Maximum vector length to print in debug messages */

/* Uncomment to produce timing messages (in some of the algorithms) */
//#define TIMING

/* Includes for parallel computation */
#ifdef _OPENMP
    #include <omp.h>
#else
    #define omp_get_thread_num() 0
    #define omp_get_max_threads() 1
    #define omp_set_num_threads(nThreads) /**/;
#endif

/* Indexes of info structure of solvers */
#define N_INFO 3
#define INFO_ITERS 0
#define INFO_GAP 1
#define INFO_RC 2

/* Comparison tolerance */
#define EPSILON 1e-10
#define IS_ZERO(x) (x < EPSILON & x > -EPSILON)
#define IS_POSITIVE(x) (x > EPSILON)
#define IS_NEGATIVE(x) (x < -EPSILON)

/* Return Codes */
#define RC_OK 0 // Solution found at the specified error level
#define RC_ITERS 1 // Maximum number of iterations reaches, result might be suboptimal
#define RC_STUCK 2 // Algorithm stuck, impossible to improve the objetive function further, result might be suboptimal
#define RC_ERROR 3 // Fatal error during the algorithm, value of the solution is undefined.

#endif


///
/// @file
///
/// @brief This file contains math functions that are shared among all
/// components of the library.
///
/// @author Mirko Myllykoski (mirkom@cs.umu.se), Umeå University
///
/// @internal LICENSE
///
/// Copyright (c) 2019-2020, Umeå Universitet
///
/// Redistribution and use in source and binary forms, with or without
/// modification, are permitted provided that the following conditions are met:
///
/// 1. Redistributions of source code must retain the above copyright notice,
///    this list of conditions and the following disclaimer.
///
/// 2. Redistributions in binary form must reproduce the above copyright notice,
///    this list of conditions and the following disclaimer in the documentation
///    and/or other materials provided with the distribution.
///
/// 3. Neither the name of the copyright holder nor the names of its
///    contributors may be used to endorse or promote products derived from this
///    software without specific prior written permission.
///
/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
/// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
/// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
/// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
/// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
/// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
/// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
/// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
/// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
/// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
/// POSSIBILITY OF SUCH DAMAGE.
///

#include <starneig_config.h>
#include <starneig/configuration.h>
#include "math.h"
#include "common.h"
#include "sanity.h"
#include <stddef.h>
#include <string.h>
#include <cblas.h>

int starneig_largers_factor(int a, int b)
{
    while (b != 0) {
        int t = b;
        b = a % b;
        a = t;
    }
    return a;
}

void starneig_init_local_q(int n, size_t ldA, double *A)
{
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            A[i*ldA+j] = 0.0;

    for (int i = 0; i < n; i++)
        A[i*ldA+i] = 1.0;
}

void starneig_small_left_gemm_update(int rbegin, int rend, int cbegin, int cend,
    size_t ldQ, size_t ldA, size_t ldT, double const *Q, double *A, double *T) {

    STARNEIG_SANITY_CHECK_INF(rbegin, rend, cbegin, cend, ldA, A, "A (in)");
    STARNEIG_SANITY_CHECK_INF(0, rend-rbegin, 0, rend-rbegin, ldQ, Q, "Q");

    int m = rend-rbegin;
    int n = cend-cbegin;
    int k = rend-rbegin;

    if (m == 0 || n == 0)
        return;

    starneig_copy_matrix(
        k, n, ldA, ldT, sizeof(double), A+cbegin*ldA+rbegin, T);

    cblas_dgemm(
        CblasColMajor, CblasTrans, CblasNoTrans, m, n, k, 1.0, Q, ldQ, T, ldT,
        0.0, A+cbegin*ldA+rbegin, ldA);

    STARNEIG_SANITY_CHECK_INF(rbegin, rend, cbegin, cend, ldA, A, "A (out)");
}

void starneig_small_right_gemm_update(
    int rbegin, int rend, int cbegin, int cend,
    size_t ldQ, size_t ldA, size_t ldT, double const *Q, double *A, double *T) {

    STARNEIG_SANITY_CHECK_INF(rbegin, rend, cbegin, cend, ldA, A, "A (in)");
    STARNEIG_SANITY_CHECK_INF(0, cend-cbegin, 0, cend-cbegin, ldQ, Q, "Q");

    int m = rend-rbegin;
    int n = cend-cbegin;
    int k = cend-cbegin;

    if (m == 0 || n == 0)
        return;

    starneig_copy_matrix(
        m, k, ldA, ldT, sizeof(double), A+cbegin*ldA+rbegin, T);

    cblas_dgemm(
        CblasColMajor, CblasNoTrans, CblasNoTrans, m, n, k, 1.0, T, ldT, Q, ldQ,
        0.0, A+cbegin*ldA+rbegin, ldA);

    STARNEIG_SANITY_CHECK_INF(rbegin, rend, cbegin, cend, ldA, A, "A (out)");
}

void starneig_small_gemm_updates(
    int begin, int end, int n, size_t ldlQ, size_t ldlZ, size_t ldQ, size_t ldZ,
    size_t ldA, size_t ldB, size_t ldhT, size_t ldvT, double const *lQ,
    double const *lZ, double *Q, double *Z, double *A, double *B,
    double *hT, double *vT)
{
    // apply the local transformation matrices lQ and lZ to Q and Z
    if (Q != NULL)
        starneig_small_right_gemm_update(
            0, n, begin, end, ldlQ, ldQ, ldvT, lQ, Q, vT);
    if (Z != NULL && Z != Q)
        starneig_small_right_gemm_update(
            0, n, begin, end, ldlZ, ldZ, ldvT, lZ, Z, vT);

    // apply the local transformation matrices lQ and lZ to A
    if (A != NULL) {
        starneig_small_right_gemm_update(
            0, begin, begin, end, ldlZ, ldA, ldvT, lZ, A, vT);
        starneig_small_left_gemm_update(
            begin, end, end, n, ldlQ, ldA, ldhT, lQ, A, hT);
    }

    // apply the local transformation matrices lQ and lZ to B
    if (B != NULL) {
        starneig_small_right_gemm_update(
            0, begin, begin, end, ldlZ, ldB, ldvT, lZ, B, vT);
        starneig_small_left_gemm_update(
            begin, end, end, n, ldlQ, ldB, ldhT, lQ, B, hT);
    }
}

void starneig_compute_complex_eigenvalue(
    int ldA, int ldB, double const *A, double const *B,
    double *real1, double *imag1, double *real2, double *imag2,
    double *beta1, double *beta2)
{
    if (B != NULL) {
        extern void dlag2_(double const *, int const *, double const *,
            int const *, double const *, double*, double *, double *, double *,
            double *);

        extern double dlamch_(char const *);
        const double safmin = dlamch_("S");

        double _real1, _real2, _beta1, _beta2, wi;
        dlag2_(
            A, &ldA, B, &ldB, &safmin, &_beta1, &_beta2, &_real1, &_real2, &wi);

        if (beta1) {
            *real1 = _real1;
            *real2 = _real2;
            *imag1 = wi;
            *imag2 = -wi;
            *beta1 = _beta1;
            *beta2 = _beta2;
        }
        else {
            *real1 = _real1/_beta1;
            *real2 = _real2/_beta2;
            *imag1 = wi/_beta1;
            *imag2 = -wi/_beta2;
        }
    }
    else {
        extern void dlanv2_(
            double *, double *, double *, double *, double *,
            double *, double *, double *, double *, double *);

        double a[] = { A[0], A[1], A[ldA], A[ldA+1] };
        double cs, ss;

        dlanv2_(
            &a[0], &a[2], &a[1], &a[3], real1, imag1, real2, imag2, &cs, &ss);

        if (beta1) {
            *beta1 = 1.0;
            *beta2 = 1.0;
        }
    }
}


#include <gsl/gsl_errno.h>
#include <gsl/matrix/gsl_matrix.h>
#include <gsl/vector/gsl_vector.h>

#define BASE_DOUBLE
#include <gsl/templates_on.h>
#include <gsl/matrix/getset_source.c>
#include <gsl/templates_off.h>
#undef BASE_DOUBLE


#include <gsl/gsl_test.h>
#include <gsl/gsl_ieee_utils.h>
#include <gsl/gsl_math.h>
#include "gsl_cblas.h"

#include "tests.h"

void
test_syrk (void) {
const double flteps = 1e-4, dbleps = 1e-6;
  {
   int order = 101;
   int uplo = 121;
   int trans = 111;
   int N = 2;
   int K = 1;
   float alpha = -1.0f;
   float beta = 0.1f;
   float A[] = { 0.412f, -0.229f };
   int lda = 1;
   float C[] = { 0.628f, -0.664f, -0.268f, 0.096f };
   int ldc = 2;
   float C_expected[] = { -0.106944f, 0.027948f, -0.268f, -0.042841f };
   cblas_ssyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], flteps, "ssyrk(case 1566)");
     }
   };
  };

  {
   int order = 102;
   int uplo = 121;
   int trans = 111;
   int N = 2;
   int K = 1;
   float alpha = -1.0f;
   float beta = 0.1f;
   float A[] = { 0.101f, -0.653f };
   int lda = 2;
   float C[] = { 0.432f, 0.107f, -0.952f, -0.532f };
   int ldc = 2;
   float C_expected[] = { 0.032999f, 0.107f, -0.029247f, -0.479609f };
   cblas_ssyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], flteps, "ssyrk(case 1567)");
     }
   };
  };

  {
   int order = 101;
   int uplo = 121;
   int trans = 112;
   int N = 2;
   int K = 1;
   float alpha = 1.0f;
   float beta = 0.1f;
   float A[] = { 0.79f, 0.595f };
   int lda = 2;
   float C[] = { 0.257f, 0.183f, -0.021f, -0.053f };
   int ldc = 2;
   float C_expected[] = { 0.6498f, 0.48835f, -0.021f, 0.348725f };
   cblas_ssyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], flteps, "ssyrk(case 1568)");
     }
   };
  };

  {
   int order = 102;
   int uplo = 121;
   int trans = 112;
   int N = 2;
   int K = 1;
   float alpha = 1.0f;
   float beta = 0.1f;
   float A[] = { -0.181f, -0.654f };
   int lda = 1;
   float C[] = { -0.4f, 0.615f, 0.147f, -0.163f };
   int ldc = 2;
   float C_expected[] = { -0.007239f, 0.615f, 0.133074f, 0.411416f };
   cblas_ssyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], flteps, "ssyrk(case 1569)");
     }
   };
  };

  {
   int order = 101;
   int uplo = 122;
   int trans = 111;
   int N = 2;
   int K = 1;
   float alpha = 0.0f;
   float beta = -1.0f;
   float A[] = { -0.191f, 0.584f };
   int lda = 1;
   float C[] = { -0.719f, -0.681f, -0.003f, 0.544f };
   int ldc = 2;
   float C_expected[] = { 0.719f, -0.681f, 0.003f, -0.544f };
   cblas_ssyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], flteps, "ssyrk(case 1570)");
     }
   };
  };

  {
   int order = 102;
   int uplo = 122;
   int trans = 111;
   int N = 2;
   int K = 1;
   float alpha = 0.0f;
   float beta = -1.0f;
   float A[] = { 0.788f, 0.041f };
   int lda = 2;
   float C[] = { 0.029f, 0.365f, 0.739f, -0.769f };
   int ldc = 2;
   float C_expected[] = { -0.029f, -0.365f, 0.739f, 0.769f };
   cblas_ssyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], flteps, "ssyrk(case 1571)");
     }
   };
  };

  {
   int order = 101;
   int uplo = 122;
   int trans = 112;
   int N = 2;
   int K = 1;
   float alpha = -0.3f;
   float beta = -1.0f;
   float A[] = { 0.733f, 0.678f };
   int lda = 2;
   float C[] = { -0.941f, 0.96f, 0.07f, -0.295f };
   int ldc = 2;
   float C_expected[] = { 0.779813f, 0.96f, -0.219092f, 0.157095f };
   cblas_ssyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], flteps, "ssyrk(case 1572)");
     }
   };
  };

  {
   int order = 102;
   int uplo = 122;
   int trans = 112;
   int N = 2;
   int K = 1;
   float alpha = -0.3f;
   float beta = -1.0f;
   float A[] = { -0.87f, 0.675f };
   int lda = 1;
   float C[] = { -0.602f, -0.432f, -0.984f, 0.384f };
   int ldc = 2;
   float C_expected[] = { 0.37493f, 0.608175f, -0.984f, -0.520687f };
   cblas_ssyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], flteps, "ssyrk(case 1573)");
     }
   };
  };

  {
   int order = 101;
   int uplo = 121;
   int trans = 111;
   int N = 2;
   int K = 1;
   double alpha = 0.1;
   double beta = -0.3;
   double A[] = { 0.169, -0.875 };
   int lda = 1;
   double C[] = { 0.159, 0.277, 0.865, 0.346 };
   int ldc = 2;
   double C_expected[] = { -0.0448439, -0.0978875, 0.865, -0.0272375 };
   cblas_dsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], dbleps, "dsyrk(case 1574)");
     }
   };
  };

  {
   int order = 102;
   int uplo = 121;
   int trans = 111;
   int N = 2;
   int K = 1;
   double alpha = 0.1;
   double beta = -0.3;
   double A[] = { 0.536, -0.725 };
   int lda = 2;
   double C[] = { 0.154, -0.445, -0.841, -0.91 };
   int ldc = 2;
   double C_expected[] = { -0.0174704, -0.445, 0.21344, 0.3255625 };
   cblas_dsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], dbleps, "dsyrk(case 1575)");
     }
   };
  };

  {
   int order = 101;
   int uplo = 121;
   int trans = 112;
   int N = 2;
   int K = 1;
   double alpha = 0;
   double beta = -1;
   double A[] = { -0.07, 0.8 };
   int lda = 2;
   double C[] = { 0.823, -0.88, -0.136, 0.793 };
   int ldc = 2;
   double C_expected[] = { -0.823, 0.88, -0.136, -0.793 };
   cblas_dsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], dbleps, "dsyrk(case 1576)");
     }
   };
  };

  {
   int order = 102;
   int uplo = 121;
   int trans = 112;
   int N = 2;
   int K = 1;
   double alpha = 0;
   double beta = -1;
   double A[] = { -0.058, 0.649 };
   int lda = 1;
   double C[] = { -0.187, 0.294, -0.004, -0.933 };
   int ldc = 2;
   double C_expected[] = { 0.187, 0.294, 0.004, 0.933 };
   cblas_dsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], dbleps, "dsyrk(case 1577)");
     }
   };
  };

  {
   int order = 101;
   int uplo = 122;
   int trans = 111;
   int N = 2;
   int K = 1;
   double alpha = 1;
   double beta = -1;
   double A[] = { 0.263, -0.289 };
   int lda = 1;
   double C[] = { 0.554, -0.679, 0.993, 0.758 };
   int ldc = 2;
   double C_expected[] = { -0.484831, -0.679, -1.069007, -0.674479 };
   cblas_dsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], dbleps, "dsyrk(case 1578)");
     }
   };
  };

  {
   int order = 102;
   int uplo = 122;
   int trans = 111;
   int N = 2;
   int K = 1;
   double alpha = 1;
   double beta = -1;
   double A[] = { -0.265, -0.837 };
   int lda = 2;
   double C[] = { -0.994, 0.967, -0.34, -0.069 };
   int ldc = 2;
   double C_expected[] = { 1.064225, -0.745195, -0.34, 0.769569 };
   cblas_dsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], dbleps, "dsyrk(case 1579)");
     }
   };
  };

  {
   int order = 101;
   int uplo = 122;
   int trans = 112;
   int N = 2;
   int K = 1;
   double alpha = -0.3;
   double beta = 1;
   double A[] = { -0.464, 0.394 };
   int lda = 2;
   double C[] = { -0.45, -0.447, 0.649, 0.055 };
   int ldc = 2;
   double C_expected[] = { -0.5145888, -0.447, 0.7038448, 0.0084292 };
   cblas_dsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], dbleps, "dsyrk(case 1580)");
     }
   };
  };

  {
   int order = 102;
   int uplo = 122;
   int trans = 112;
   int N = 2;
   int K = 1;
   double alpha = -0.3;
   double beta = 1;
   double A[] = { 0.815, 0.168 };
   int lda = 1;
   double C[] = { 0.817, -0.957, -0.395, -0.382 };
   int ldc = 2;
   double C_expected[] = { 0.6177325, -0.998076, -0.395, -0.3904672 };
   cblas_dsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[i], C_expected[i], dbleps, "dsyrk(case 1581)");
     }
   };
  };

  {
   int order = 101;
   int uplo = 121;
   int trans = 111;
   int N = 2;
   int K = 1;
   float alpha[2] = {0.0f, 0.0f};
   float beta[2] = {-0.3f, 0.1f};
   float A[] = { 0.447f, -0.507f, -0.425f, 0.701f };
   int lda = 1;
   float C[] = { 0.16f, -0.245f, 0.922f, -0.437f, 0.24f, 0.008f, -0.095f, 0.749f };
   int ldc = 2;
   float C_expected[] = { -0.0235f, 0.0895f, -0.2329f, 0.2233f, 0.24f, 0.008f, -0.0464f, -0.2342f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1582) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1582) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 121;
   int trans = 111;
   int N = 2;
   int K = 1;
   float alpha[2] = {0.0f, 0.0f};
   float beta[2] = {-0.3f, 0.1f};
   float A[] = { -0.421f, -0.435f, -0.914f, -0.493f };
   int lda = 2;
   float C[] = { -0.761f, -0.38f, 0.043f, -0.999f, 0.779f, 0.238f, 0.082f, 0.394f };
   int ldc = 2;
   float C_expected[] = { 0.2663f, 0.0379f, 0.043f, -0.999f, -0.2575f, 0.0065f, -0.064f, -0.11f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1583) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1583) imag");
     };
   };
  };

  {
   int order = 101;
   int uplo = 121;
   int trans = 112;
   int N = 2;
   int K = 1;
   float alpha[2] = {-1.0f, 0.0f};
   float beta[2] = {-0.3f, 0.1f};
   float A[] = { 0.827f, -0.896f, 0.417f, 0.865f };
   int lda = 2;
   float C[] = { -0.349f, -0.31f, 0.972f, 0.794f, -0.906f, -0.595f, -0.089f, -0.333f };
   int ldc = 2;
   float C_expected[] = { 0.254587f, 1.54008f, -1.4909f, -0.482723f, -0.906f, -0.595f, 0.634336f, -0.63041f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1584) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1584) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 121;
   int trans = 112;
   int N = 2;
   int K = 1;
   float alpha[2] = {-1.0f, 0.0f};
   float beta[2] = {-0.3f, 0.1f};
   float A[] = { 0.607f, 0.747f, -0.889f, 0.333f };
   int lda = 1;
   float C[] = { 0.244f, 0.564f, 0.009f, 0.578f, -0.827f, 0.558f, -0.337f, 0.731f };
   int ldc = 2;
   float C_expected[] = { 0.05996f, -1.05166f, 0.009f, 0.578f, 0.980674f, 0.211852f, -0.651432f, 0.339074f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1585) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1585) imag");
     };
   };
  };

  {
   int order = 101;
   int uplo = 121;
   int trans = 113;
   int N = 2;
   int K = 1;
   float alpha[2] = {1.0f, 0.0f};
   float beta[2] = {0.0f, 1.0f};
   float A[] = { 0.784f, -0.281f, -0.88f, 0.479f };
   int lda = 2;
   float C[] = { 0.491f, 0.531f, 0.805f, -0.097f, 0.728f, 0.674f, -0.705f, -0.754f };
   int ldc = 2;
   float C_expected[] = { 0.004695f, 0.050392f, -0.458321f, 1.42782f, 0.728f, 0.674f, 1.29896f, -1.54804f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1586) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1586) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 121;
   int trans = 113;
   int N = 2;
   int K = 1;
   float alpha[2] = {1.0f, 0.0f};
   float beta[2] = {0.0f, 1.0f};
   float A[] = { 0.272f, -0.146f, 0.155f, 0.038f };
   int lda = 1;
   float C[] = { 0.533f, -0.41f, -0.904f, 0.301f, -0.836f, 0.57f, -0.374f, -0.293f };
   int ldc = 2;
   float C_expected[] = { 0.462668f, 0.453576f, -0.904f, 0.301f, -0.522292f, -0.848294f, 0.315581f, -0.36222f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1587) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1587) imag");
     };
   };
  };

  {
   int order = 101;
   int uplo = 122;
   int trans = 111;
   int N = 2;
   int K = 1;
   float alpha[2] = {0.0f, 1.0f};
   float beta[2] = {-1.0f, 0.0f};
   float A[] = { -0.055f, -0.127f, -0.896f, -0.625f };
   int lda = 1;
   float C[] = { -0.619f, 0.511f, -0.877f, 0.557f, -0.801f, -0.437f, -0.922f, 0.332f };
   int ldc = 2;
   float C_expected[] = { 0.60503f, -0.524104f, -0.877f, 0.557f, 0.652833f, 0.406905f, -0.198f, 0.080191f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1588) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1588) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 122;
   int trans = 111;
   int N = 2;
   int K = 1;
   float alpha[2] = {0.0f, 1.0f};
   float beta[2] = {-1.0f, 0.0f};
   float A[] = { -0.528f, 0.759f, -0.079f, 0.952f };
   int lda = 2;
   float C[] = { 0.775f, 0.855f, 0.786f, 0.525f, 0.85f, 0.044f, 0.658f, 0.947f };
   int ldc = 2;
   float C_expected[] = { 0.026504f, -1.1523f, -0.223383f, -1.20586f, 0.85f, 0.044f, -0.507584f, -1.84706f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1589) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1589) imag");
     };
   };
  };

  {
   int order = 101;
   int uplo = 122;
   int trans = 112;
   int N = 2;
   int K = 1;
   float alpha[2] = {1.0f, 0.0f};
   float beta[2] = {1.0f, 0.0f};
   float A[] = { -0.049f, -0.687f, -0.434f, 0.294f };
   int lda = 2;
   float C[] = { 0.937f, -0.113f, 0.796f, 0.293f, 0.876f, -0.199f, -0.757f, -0.103f };
   int ldc = 2;
   float C_expected[] = { 0.467432f, -0.045674f, 0.796f, 0.293f, 1.09924f, 0.084752f, -0.65508f, -0.358192f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1590) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1590) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 122;
   int trans = 112;
   int N = 2;
   int K = 1;
   float alpha[2] = {1.0f, 0.0f};
   float beta[2] = {1.0f, 0.0f};
   float A[] = { 0.359f, -0.364f, 0.926f, -0.69f };
   int lda = 1;
   float C[] = { 0.306f, 0.249f, 0.28f, 0.229f, 0.866f, 0.092f, 0.886f, -0.283f };
   int ldc = 2;
   float C_expected[] = { 0.302385f, -0.012352f, 0.361274f, -0.355774f, 0.866f, 0.092f, 1.26738f, -1.56088f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1591) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1591) imag");
     };
   };
  };

  {
   int order = 101;
   int uplo = 122;
   int trans = 113;
   int N = 2;
   int K = 1;
   float alpha[2] = {-0.3f, 0.1f};
   float beta[2] = {0.0f, 0.0f};
   float A[] = { 0.607f, 0.555f, -0.85f, 0.831f };
   int lda = 2;
   float C[] = { 0.069f, 0.368f, 0.551f, -0.912f, -0.243f, -0.063f, -0.924f, 0.192f };
   int ldc = 2;
   float C_expected[] = { -0.0855042f, -0.196089f, 0.551f, -0.912f, 0.28988f, -0.107516f, 0.131688f, 0.427004f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1592) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1592) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 122;
   int trans = 113;
   int N = 2;
   int K = 1;
   float alpha[2] = {-0.3f, 0.1f};
   float beta[2] = {0.0f, 0.0f};
   float A[] = { 0.427f, 0.86f, -0.136f, 0.002f };
   int lda = 1;
   float C[] = { 0.398f, -0.47f, 0.011f, -0.547f, -0.106f, 0.016f, 0.681f, 0.246f };
   int ldc = 2;
   float C_expected[] = { 0.0937373f, -0.276059f, 0.0295482f, 0.0288526f, -0.106f, 0.016f, -0.0054932f, 0.0020124f };
   cblas_csyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], flteps, "csyrk(case 1593) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], flteps, "csyrk(case 1593) imag");
     };
   };
  };

  {
   int order = 101;
   int uplo = 121;
   int trans = 111;
   int N = 2;
   int K = 1;
   double alpha[2] = {-0.3, 0.1};
   double beta[2] = {1, 0};
   double A[] = { 0.718, 0.023, 0.355, -0.492 };
   int lda = 1;
   double C[] = { -0.637, -0.727, -0.475, -0.776, 0.802, -0.55, -0.837, 0.222 };
   int ldc = 2;
   double C_expected[] = { -0.7948013, -0.6854089, -0.5203527, -0.6458521, 0.802, -0.55, -0.7672563, 0.3151921 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1594) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1594) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 121;
   int trans = 111;
   int N = 2;
   int K = 1;
   double alpha[2] = {-0.3, 0.1};
   double beta[2] = {1, 0};
   double A[] = { 0.209, 0.139, -0.202, -0.223 };
   int lda = 2;
   double C[] = { -0.695, 0.524, 0.212, -0.88, -0.752, 0.291, 0.684, -0.124 };
   int ldc = 2;
   double C_expected[] = { -0.7081182, 0.5090054, 0.212, -0.88, -0.7411652, 0.3122834, 0.6776683, -0.1519201 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1595) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1595) imag");
     };
   };
  };

  {
   int order = 101;
   int uplo = 121;
   int trans = 112;
   int N = 2;
   int K = 1;
   double alpha[2] = {-0.3, 0.1};
   double beta[2] = {1, 0};
   double A[] = { -0.365, -0.624, 0.632, 0.348 };
   int lda = 2;
   double C[] = { 0.877, 0.927, -0.377, 0.967, 0.008, 0.292, -0.779, 0.794 };
   int ldc = 2;
   double C_expected[] = { 0.9082933, 0.7647289, -0.3208028, 1.1220636, 0.008, 0.292, -0.9064832, 0.6898704 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1596) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1596) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 121;
   int trans = 112;
   int N = 2;
   int K = 1;
   double alpha[2] = {-0.3, 0.1};
   double beta[2] = {1, 0};
   double A[] = { -0.067, -0.586, 0.208, 0.331 };
   int lda = 1;
   double C[] = { 0.584, -0.454, 0.93, 0.782, 0.489, -0.278, 0.081, -0.919 };
   int ldc = 2;
   double C_expected[] = { 0.6778197, -0.5114479, 0.93, 0.782, 0.4493975, -0.2167775, 0.0871195, -0.9669385 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1597) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1597) imag");
     };
   };
  };

  {
   int order = 101;
   int uplo = 121;
   int trans = 113;
   int N = 2;
   int K = 1;
   double alpha[2] = {0, 0.1};
   double beta[2] = {0, 0.1};
   double A[] = { -0.617, 0.179, -0.626, 0.334 };
   int lda = 2;
   double C[] = { 0.346, -0.903, 0.022, -0.839, -0.715, 0.049, -0.338, 0.149 };
   int ldc = 2;
   double C_expected[] = { 0.1123886, 0.0694648, 0.1157132, 0.0348456, -0.715, 0.049, 0.0269168, -0.005768 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1598) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1598) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 121;
   int trans = 113;
   int N = 2;
   int K = 1;
   double alpha[2] = {0, 0.1};
   double beta[2] = {0, 0.1};
   double A[] = { -0.356, -0.308, 0.493, -0.351 };
   int lda = 1;
   double C[] = { -0.898, -0.905, 0.002, -0.219, 0.881, 0.879, 0.275, -0.351 };
   int ldc = 2;
   double C_expected[] = { 0.0685704, -0.0866128, 0.002, -0.219, -0.0852112, 0.0597384, 0.0697086, 0.0394848 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1599) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1599) imag");
     };
   };
  };

  {
   int order = 101;
   int uplo = 122;
   int trans = 111;
   int N = 2;
   int K = 1;
   double alpha[2] = {0, 0.1};
   double beta[2] = {1, 0};
   double A[] = { -0.103, -0.951, -0.601, -0.041 };
   int lda = 1;
   double C[] = { -0.918, -0.018, 0.991, -0.789, -0.698, -0.067, 0.956, -0.599 };
   int ldc = 2;
   double C_expected[] = { -0.9375906, -0.1073792, 0.991, -0.789, -0.7555774, -0.0647088, 0.9510718, -0.563048 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1600) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1600) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 122;
   int trans = 111;
   int N = 2;
   int K = 1;
   double alpha[2] = {0, 0.1};
   double beta[2] = {1, 0};
   double A[] = { -0.237, 0.925, -0.904, -0.091 };
   int lda = 2;
   double C[] = { -0.572, 0.915, 0.398, 0.222, 0.016, 0.288, -0.078, -0.507 };
   int ldc = 2;
   double C_expected[] = { -0.528155, 0.8350544, 0.4794633, 0.2518423, 0.016, 0.288, -0.0944528, -0.4261065 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1601) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1601) imag");
     };
   };
  };

  {
   int order = 101;
   int uplo = 122;
   int trans = 112;
   int N = 2;
   int K = 1;
   double alpha[2] = {-0.3, 0.1};
   double beta[2] = {0, 0};
   double A[] = { 0.963, -0.23, -0.435, 0.289 };
   int lda = 2;
   double C[] = { 0.282, -0.272, -0.516, -0.594, -0.001, 0.155, -0.39, -0.354 };
   int ldc = 2;
   double C_expected[] = { -0.2180427, 0.2203409, -0.516, -0.594, 0.0678948, -0.1487506, -0.0065682, 0.0859994 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1602) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1602) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 122;
   int trans = 112;
   int N = 2;
   int K = 1;
   double alpha[2] = {-0.3, 0.1};
   double beta[2] = {0, 0};
   double A[] = { 0.674, 0.1, -0.098, 0.552 };
   int lda = 1;
   double C[] = { 0.089, -0.523, -0.551, 0.618, 0.67, 0.247, 0.975, -0.714 };
   int ldc = 2;
   double C_expected[] = { -0.1467628, 0.0039876, 0.0001508, -0.1207996, 0.67, 0.247, 0.0993492, 0.0029476 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1603) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1603) imag");
     };
   };
  };

  {
   int order = 101;
   int uplo = 122;
   int trans = 113;
   int N = 2;
   int K = 1;
   double alpha[2] = {0, 1};
   double beta[2] = {1, 0};
   double A[] = { 0.033, -0.864, 0.168, 0.524 };
   int lda = 2;
   double C[] = { 0.788, 0.016, -0.436, 0.749, -0.89, -0.87, 0.421, -0.203 };
   int ldc = 2;
   double C_expected[] = { 0.845024, -0.729407, -0.436, 0.749, -0.76214, -0.41172, 0.244936, -0.449352 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1604) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1604) imag");
     };
   };
  };

  {
   int order = 102;
   int uplo = 122;
   int trans = 113;
   int N = 2;
   int K = 1;
   double alpha[2] = {0, 1};
   double beta[2] = {1, 0};
   double A[] = { 0.957, -0.079, 0.935, 0.232 };
   int lda = 1;
   double C[] = { -0.744, -0.061, 0.195, -0.574, 0.551, 0.478, -0.337, 0.1 };
   int ldc = 2;
   double C_expected[] = { -0.592794, 0.848608, 0.046841, 0.339123, 0.551, 0.478, -0.77084, 0.920401 };
   cblas_zsyrk(order, uplo, trans, N, K, alpha, A, lda, beta, C, ldc);
   {
     int i;
     for (i = 0; i < 4; i++) {
       gsl_test_rel(C[2*i], C_expected[2*i], dbleps, "zsyrk(case 1605) real");
       gsl_test_rel(C[2*i+1], C_expected[2*i+1], dbleps, "zsyrk(case 1605) imag");
     };
   };
  };

}


// Copyright András Vukics 2006–2020. Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE.txt)
/// \briefFile{Random & related}
#ifndef CPPQEDCORE_UTILS_RANDOM_H_REENTRANT

#ifndef CPPQEDCORE_UTILS_RANDOM_H_INCLUDED
#define CPPQEDCORE_UTILS_RANDOM_H_INCLUDED

#include "Pars.h"

#include "pcg_random.hpp"
#include "XoshiroCpp.hpp"

#ifdef CPPQED_HAS_GSL
#include <gsl/gsl_rng.h>
#endif // CPPQED_HAS_GSL

#include <boost/range/algorithm/generate.hpp>

#ifdef BZ_HAVE_BOOST_SERIALIZATION
#include <boost/serialization/string.hpp>
#include <boost/serialization/array.hpp>
#endif // BZ_HAVE_BOOST_SERIALIZATION

#include <memory>
#include <optional>
#include <random>
#include <stdexcept>
#include <sstream>
#include <optional>

namespace randomutils {

template<typename Engine, typename BASE>
struct Pars;

#ifdef CPPQED_HAS_GSL

/// Wraps GSL random number generators into the concept [UniformRandomBitGenerator](http://en.cppreference.com/w/cpp/named_req/UniformRandomBitGenerator)
class GSL_Engine
{
public:
  using result_type=unsigned long;

  explicit GSL_Engine(result_type s/*, const gsl_rng_type* ran_gen_type=gsl_rng_taus2*/);

  void seed(result_type value);

  result_type operator()();

  result_type min() const;
  result_type max() const;

  void write(std::ostream& os) const;

  void read(std::istream& is);

private:
  const std::shared_ptr<gsl_rng> ranGen_;
  const std::string name_;

};

inline std::ostream& operator<<(std::ostream& os, const GSL_Engine& r) {r.write(os); return os;}
inline std::istream& operator>>(std::istream& is, GSL_Engine& r) {r.read(is); return is;}

#define CPPQEDCORE_UTILS_RANDOM_H_REENTRANT
#define CPPQEDCORE_UTILS_RANDOM_H_RANDOMENGINE GSL_Engine
#define CPPQEDCORE_UTILS_RANDOM_H_STRING "GSL_Engine state"

#include "Random.h"

} // randomutils

#endif // CPPQED_HAS_GSL

namespace randomutils {

template<typename Distribution, typename Engine, typename Array, typename... DCP>
Engine& fill(Array a, Engine& re, DCP&&... dcp)
{
  Distribution d{std::forward<DCP>(dcp)...};
  std::generate(a.begin(),a.end(),[&]() {return d(re);});
  return re;
}

template<typename Distribution, typename Engine, typename Array, typename... DCP>
void fill(Array a, unsigned long seed, DCP&&... dcp)
{
  Engine re{seed};
  fill<Distribution>(a,re,std::forward<DCP>(dcp)...);
}

template<typename Engine>
constexpr auto EngineID_v = std::nullopt;

template<>
inline const std::string EngineID_v<GSL_Engine> = "GSL_Taus2";

template<>
inline const std::string EngineID_v<std::mt19937_64> = "STD_MT19937_64";

template<>
inline const std::string EngineID_v<pcg64> = "PCG64";

template<>
inline const std::string EngineID_v<XoshiroCpp::Xoshiro256PlusPlus> = "Xoshiro256pp";

template<typename Engine>
struct EngineWithParameters
{
  EngineWithParameters(unsigned long s, unsigned long p) : engine{s,p}, seed{s}, prngStream{p} {}

  std::ostream& stream(std::ostream& os) const {return os << "Random engine: "<<EngineID_v<Engine><<". Parameters: seed="<<seed<<", streamOrdo=" << prngStream;}

  Engine engine;
  const unsigned long seed, prngStream;

};

// template<>
// inline std::list<pcg64> independentPRNG_Streams<pcg64>(size_t n, pcg64::result_type seed, pcg64::result_type ordoStream)
// {
//   std::list<pcg64> res;
//   for (size_t i=0; i<n; ++i) res.emplace_back(seed,1001+ordoStream+i);
//   return res;
// }

template<typename BASE>
struct Pars<pcg64,BASE> : BASE
{
  unsigned long &seed, &prngStream; ///< PCG64 generator seed and ordinal of independent stream

  Pars(parameters::Table& p, const std::string& mod="")
    : BASE{p,mod},
      seed(p.addTitle("StochasticTrajectory",mod).add("seed",mod,"Random number generator seed",1001ul)),
      prngStream(p.add("prngStream",mod,"Random number generator independent stream ordinal",1ul))
    {}
};

#ifndef NDEBUG
#pragma GCC warning "TODO: implement nextStream optionally from previousState"
#endif // NDEBUG
template<typename BASE>
pcg64 streamOfOrdo(const Pars<pcg64,BASE>& p/*, std::optional<EngineState> previousState = std::nullopt*/)
{
  return pcg64{p.seed,1001+p.prngStream};
}

template<typename BASE>
void incrementForNextStream(const Pars<pcg64,BASE>& p) {++p.prngStream;}

} // randomutils

namespace boost { namespace serialization {

template<typename Ar>
void load(Ar& ar, XoshiroCpp::Xoshiro256PlusPlus& xpp, unsigned) {
  XoshiroCpp::Xoshiro256PlusPlus::state_type state;
  ar & state;
  xpp.deserialize(state);
}

template<typename Ar>
void save(Ar& ar, XoshiroCpp::Xoshiro256PlusPlus const& xpp, unsigned) {
  auto state{xpp.serialize()};
  ar & state;
}

template<typename Ar>
void serialize(Ar& ar, XoshiroCpp::Xoshiro256PlusPlus& xpp, unsigned version) {
  if (typename Ar::is_saving())
    save(ar, xpp, version);
  else
    load(ar, xpp, version);
}

#define CPPQEDCORE_UTILS_RANDOM_H_REENTRANT
#define CPPQEDCORE_UTILS_RANDOM_H_RANDOMENGINE std::mt19937_64
#define CPPQEDCORE_UTILS_RANDOM_H_STRING "mersenne_twister_engine state"

#include "Random.h"

#define CPPQEDCORE_UTILS_RANDOM_H_REENTRANT
#define CPPQEDCORE_UTILS_RANDOM_H_RANDOMENGINE pcg64
#define CPPQEDCORE_UTILS_RANDOM_H_STRING "pcg64 state"

#include "Random.h"

} } // boost::serialization

#endif // CPPQEDCORE_UTILS_RANDOM_H_INCLUDED

#else // CPPQEDCORE_UTILS_RANDOM_H_REENTRANT

template<typename Ar>
void load(Ar& ar, CPPQEDCORE_UTILS_RANDOM_H_RANDOMENGINE& randomEngine, unsigned) {
  std::string text;
  ar & text;
  std::istringstream iss(text);

  if (!(iss >> randomEngine))
    throw std::invalid_argument(CPPQEDCORE_UTILS_RANDOM_H_STRING);
}

template<typename Ar>
void save(Ar& ar, CPPQEDCORE_UTILS_RANDOM_H_RANDOMENGINE const& randomEngine, unsigned) {
  std::ostringstream oss;
  if (!(oss << randomEngine))
    throw std::invalid_argument(CPPQEDCORE_UTILS_RANDOM_H_STRING);
  std::string text = oss.str();
  ar & text;
}

template<typename Ar>
void serialize(Ar& ar, CPPQEDCORE_UTILS_RANDOM_H_RANDOMENGINE& randomEngine, unsigned version) {
  if (typename Ar::is_saving())
    save(ar, randomEngine, version);
  else
    load(ar, randomEngine, version);
}

#undef CPPQEDCORE_UTILS_RANDOM_H_STRING
#undef CPPQEDCORE_UTILS_RANDOM_H_RANDOMENGINE
#undef CPPQEDCORE_UTILS_RANDOM_H_REENTRANT

#endif // CPPQEDCORE_UTILS_RANDOM_H_REENTRANT


#include <string.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_math.h>
#include <math.h>
#include <options/options.h>

#include "../csm/csm_all.h"

struct ld_exp_tro1_params {
	int seed;

	double max_xy_error;
	double max_theta_error_deg;

	const char* file_input;
	const char* file_output1;
	const char* file_output2;

	int num_per_scan;

	int debug;
};

const char * banner =
	"This program prepares the data for one of the experiments. \n\n"
		"The input is any sensor log (Carmen or JSON format) \n"
		"The output are two files that contain laser_ref and laser_sens\n"
		"(you have to match the i-th scan in the first file with the i-th\n"
		" in the second).\n\n"
		"The two files contain exactly the same date but for the 'odometry' field\n"
		"The odometry error is uniform in the intervals given.\n";

int main(int argc, const char ** argv) {
	sm_set_program_name(argv[0]);

	struct ld_exp_tro1_params p;

	options_banner(banner);

	struct option* ops = options_allocate(10);
	options_double(ops, "max_xy_error", &p.max_xy_error, 10.0, "Maximum error for x,y (m)");
	options_double(ops, "max_theta_error_deg", &p.max_theta_error_deg, 10.0, "Maximum error for orientation (deg)");
	options_int   (ops, "seed", &p.seed, 0, "Seed for random number generator (if 0, use GSL_RNG_SEED env. variable).");

	options_int(ops, "num_per_scan", &p.num_per_scan, 10, "Number of trials for each scan.");

	options_string(ops, "in", &p.file_input, "stdin", "Input file ");
	options_string(ops, "out1", &p.file_output1, "stdout", "Output file for first scan");
	options_string(ops, "out2", &p.file_output2, "stdout", "Output file for second scan");

	options_int(ops, "debug", &p.debug, 0, "Shows debug information");

	if(!options_parse_args(ops, argc, argv)) {
		options_print_help(ops, stderr);
		return -1;
	}

	sm_debug_write(p.debug);

	gsl_rng_env_setup();
	gsl_rng * rng = gsl_rng_alloc (gsl_rng_ranlxs0);
	if(p.seed != 0)
	gsl_rng_set(rng, (unsigned int) p.seed);

	/* Open the two output files (possibly the same one) */

	FILE * in = open_file_for_reading(p.file_input);
	if(!in) return -3;

	FILE * out1 = open_file_for_writing(p.file_output1);
	if(!out1) return -2;

	FILE * out2;
	if(!strcmp(p.file_output1, p.file_output2)) {
		out1 = out2;
	} else {
		out2 = open_file_for_writing(p.file_output2);
		if(!out2) return -2;
	}

	/* Read laser data from input file */
	LDP ld; int count=0;
	while( (ld = ld_read_smart(in))) {
		count++;
		if(!ld_valid_fields(ld))  {
			sm_error("Invalid laser data (#%d in file)\n", count);
			continue;
		}

		for(int n=0; n < p.num_per_scan; n++) {
			ld->true_pose[0] = 0;
			ld->true_pose[1] = 0;
			ld->true_pose[2] = 0;

			ld->odometry[0] = 0;
			ld->odometry[1] = 0;
			ld->odometry[2] = 0;

			ld_write_as_json(ld, out1);

			ld->odometry[0] = 2*(gsl_rng_uniform(rng)-0.5) * p.max_xy_error;
			ld->odometry[1] = 2*(gsl_rng_uniform(rng)-0.5) * p.max_xy_error;
			ld->odometry[2] = 2*(gsl_rng_uniform(rng)-0.5) * deg2rad(p.max_theta_error_deg);

			ld_write_as_json(ld, out2);
		}

		ld_free(ld);
	}

	return 0;
}


#ifndef MATRIX_H
#define MATRIX_H

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>

#include <config.h>

#include "collections.h"
#include "file_utils.h"
#include "vector.h"
#include "vector_math.h"

#ifdef HAVE_CBLAS
#include <cblas.h>
#else
#warning "No CLBAS"
#endif

typedef enum {
    MATRIX_DENSE,
    MATRIX_SPARSE
} matrix_type_t;

#define MATRIX_INIT(name, type, type_name, array_type)                                                          \
    typedef struct {                                                                                \
        size_t m, n;                                                                                \
        type *values;                                                                                \
    } name##_t;                                                                                \
                                                                                \
    static name##_t *name##_new(size_t m, size_t n) {                                                           \
        name##_t *matrix = malloc(sizeof(name##_t));                                                            \
                                                                                \
        if (matrix == NULL) {                                                                                \
            return NULL;                                                                                \
        }                                                                                \
                                                                                \
        matrix->m = m;                                                                                \
        matrix->n = n;                                                                                \
                                                                                \
        matrix->values = malloc(sizeof(type) * m * n);                                                          \
        if (matrix->values == NULL) {                                                                           \
            free(matrix);                                                                                \
            return NULL;                                                                                \
        }                                                                                \
                                                                                \
        return matrix;                                                                                \
                                                                                \
    }                                                                                \
                                                                                \
    static name##_t *name##_new_aligned(size_t m, size_t n, size_t alignment) {                                 \
        name##_t *matrix = malloc(sizeof(name##_t));                                                            \
                                                                                \
        if (matrix == NULL) {                                                                                \
            return NULL;                                                                                \
        }                                                                                \
                                                                                \
        matrix->m = m;                                                                                \
        matrix->n = n;                                                                                \
                                                                                \
        matrix->values = _aligned_malloc(sizeof(type) * m * n, alignment);                                      \
        if (matrix->values == NULL) {                                                                           \
            free(matrix);                                                                                \
            return NULL;                                                                                \
        }                                                                                \
                                                                                \
        return matrix;                                                                                \
                                                                                \
    }                                                                                \
                                                                                \
    static void name##_destroy(name##_t *self) {                                                                \
        if (self == NULL) return;                                                                               \
                                                                                \
        if (self->values != NULL) {                                                                             \
            free(self->values);                                                                                \
        }                                                                                \
                                                                                \
        free(self);                                                                                \
    }                                                                                \
                                                                                \
    static void name##_destroy_aligned(name##_t *self) {                                                        \
        if (self == NULL) return;                                                                               \
                                                                                \
        if (self->values != NULL) {                                                                             \
            _aligned_free(self->values);                                                                        \
        }                                                                                \
                                                                                \
        free(self);                                                                                \
    }                                                                                \
                                                                                \
    static inline void name##_zero(name##_t *self) {                                                            \
        memset(self->values, 0, self->m * self->n * sizeof(type));                                              \
    }                                                                                \
                                                                                \
                                                                                \
    static inline bool name##_resize(name##_t *self, size_t m, size_t n) {                                      \
        if (self == NULL) return false;                                                                         \
                                                                                \
        if (m * n > (self->m * self->n)) {                                                                      \
            type *ptr = realloc(self->values, sizeof(type) * m * n);                                            \
            if (ptr == NULL) {                                                                                \
                return false;                                                                                \
            }                                                                                \
            self->values = ptr;                                                                                \
        }                                                                                \
                                                                                \
        self->m = m;                                                                                \
        self->n = n;                                                                                \
                                                                                \
        return true;                                                                                \
    }                                                                                \
                                                                                \
    static inline bool name##_resize_aligned(name##_t *self, size_t m, size_t n, size_t alignment) {            \
        if (self == NULL) return false;                                                                         \
                                                                                \
        if (m * n > (self->m * self->n)) {                                                                      \
            type *ptr = _aligned_realloc(self->values, sizeof(type) * m * n, alignment);                        \
            if (ptr == NULL) {                                                                                \
                return false;                                                                                \
            }                                                                                \
            self->values = ptr;                                                                                \
        }                                                                                \
                                                                                \
        self->m = m;                                                                                \
        self->n = n;                                                                                \
                                                                                \
        return true;                                                                                \
    }                                                                                \
                                                                                \
    static inline bool name##_resize_fill_zeros(name##_t *self, size_t m, size_t n) {                           \
        size_t old_m = self->m;                                                                                \
        bool ret = name##_resize(self, m, n);                                                                   \
        if (ret && m > old_m) {                                                                                \
            memset(self->values + old_m, 0, (m - old_m) * self->n * sizeof(type));                              \
        }                                                                                \
        return ret;                                                                                \
    }                                                                                \
                                                                                \
    static inline bool name##_resize_aligned_fill_zeros(name##_t *self, size_t m, size_t n, size_t alignment) { \
        size_t old_m = self->m;                                                                                \
        bool ret = name##_resize_aligned(self, m, n, alignment);                                                \
        if (ret && m > old_m) {                                                                                \
            memset(self->values + old_m, 0, (m - old_m) * self->n * sizeof(type));                              \
        }                                                                                \
        return ret;                                                                                \
    }                                                                                \
                                                                                \
    static inline name##_t *name##_new_copy(name##_t *self) {                                                   \
        name##_t *cpy = name##_new(self->m, self->n);                                                           \
        size_t num_values = self->m * self->n;                                                                  \
        memcpy(cpy->values, self->values, num_values * sizeof(type));                                           \
                                                                                \
        return cpy;                                                                                \
    }                                                                                \
                                                                                \
    static inline bool name##_copy(name##_t *self, name##_t *other) {                                           \
        if (self->m != other->m || self->n != other->n) {                                                       \
            return false;                                                                                \
        }                                                                                \
        size_t num_values = self->m * self->n;                                                                  \
                                                                                \
        memcpy(other->values, self->values, num_values * sizeof(type));                                         \
        return true;                                                                                \
    }                                                                                \
                                                                                \
    static inline void name##_init_values(name##_t *self, type *values) {                                       \
        size_t num_values = self->m * self->n;                                                                  \
        memcpy(self->values, values, num_values * sizeof(type));                                                \
    }                                                                                \
                                                                                \
    static inline void name##_set(name##_t *self, type value) {                                                 \
        array_type##_set(self->values, value, self->m * self->n);                                               \
    }                                                                                \
                                                                                \
    static inline void name##_set_row(name##_t *self, size_t index, type *row) {                                \
        size_t offset = index * self->n;                                                                        \
        type *values = self->values;                                                                            \
        size_t n = self->n;                                                                                \
        memcpy(values + offset, row, n * sizeof(type));                                                         \
    }                                                                                \
                                                                                \
    static inline void name##_set_scalar(name##_t *self, size_t row_index, size_t col_index, type value) {      \
        size_t offset = row_index * self->n + col_index;                                                        \
        self->values[offset] = value;                                                                           \
    }                                                                                \
                                                                                \
    static inline void name##_add_scalar(name##_t *self, size_t row_index, size_t col_index, type value) {      \
        size_t offset = row_index * self->n + col_index;                                                        \
        self->values[offset] += value;                                                                          \
    }                                                                                \
                                                                                \
    static inline void name##_sub_scalar(name##_t *self, size_t row_index, size_t col_index, type value) {      \
        size_t offset = row_index * self->n + col_index;                                                        \
        self->values[offset] -= value;                                                                          \
    }                                                                                \
                                                                                \
    static inline void name##_mul_scalar(name##_t *self, size_t row_index, size_t col_index, type value) {      \
        size_t offset = row_index * self->n + col_index;                                                        \
        self->values[offset] *= value;                                                                          \
    }                                                                                \
                                                                                \
    static inline void name##_div_scalar(name##_t *self, size_t row_index, size_t col_index, type value) {      \
        size_t offset = row_index * self->n + col_index;                                                        \
        self->values[offset] /= value;                                                                          \
    }                                                                                \
                                                                                \
    static inline type name##_get(name##_t *self, size_t row_index, size_t col_index) {                         \
        size_t index = row_index * self->n + col_index;                                                         \
        return self->values[index];                                                                             \
    }                                                                                \
                                                                                \
    static inline type *name##_get_row(name##_t *self, size_t row_index) {                                      \
        size_t index = row_index * self->n;                                                                     \
        return self->values + index;                                                                            \
    }                                                                                \
                                                                                \
    static inline name##_t *name##_new_value(size_t m, size_t n, type value) {                                  \
        name##_t *matrix = name##_new(m, n);                                                                    \
        name##_set(matrix, value);                                                                              \
        return matrix;                                                                                \
    }                                                                                \
                                                                                \
    static inline name##_t *name##_new_zeros(size_t m, size_t n) {                                              \
        name##_t *matrix = name##_new(m, n);                                                                    \
        name##_zero(matrix);                                                                                \
        return matrix;                                                                                \
    }                                                                                \
                                                                                \
    static inline name##_t *name##_new_ones(size_t m, size_t n) {                                               \
        return name##_new_value(m, n, (type)1);                                                                 \
    }                                                                                \
                                                                                \
    static inline name##_t *name##_new_values(size_t m, size_t n, type *values) {                               \
        name##_t *matrix = name##_new(m, n);                                                                    \
        memcpy(matrix->values, values, m * n * sizeof(type));                                                   \
        return matrix;                                                                                \
    }                                                                                \
                                                                                \
    static inline void name##_div(name##_t *self, type value) {                                                 \
        array_type##_div(self->values, value, self->m * self->n);                                               \
    }                                                                                \
                                                                                \
    static inline bool name##_div_matrix(name##_t *self, name##_t *other) {                                     \
        if (self->m != other->m || self->n != other->n) return false;                                           \
        array_type##_div_array(self->values, other->values, self->m * self->n);                                 \
        return true;                                                                                \
    }                                                                                \
                                                                                \
    static inline bool name##_div_matrix_times_scalar(name##_t *self, name##_t *other, type v) {                \
        if (self->m != other->m || self->n != other->n) return false;                                           \
        array_type##_div_array_times_scalar(self->values, other->values, v, self->m * self->n);                 \
        return true;                                                                                \
    }                                                                                \
                                                                                \
    static inline void name##_mul(name##_t *self, type value) {                                                 \
        array_type##_mul(self->values, value, self->m * self->n);                                               \
    }                                                                                \
                                                                                \
    static inline bool name##_mul_matrix(name##_t *self, name##_t *other) {                                     \
        if (self->m != other->m || self->n != other->n) return false;                                           \
        array_type##_mul_array(self->values, other->values, self->m * self->n);                                 \
        return true;                                                                                \
    }                                                                                \
                                                                                \
    static inline bool name##_mul_matrix_times_scalar(name##_t *self, name##_t *other, type v) {                \
        if (self->m != other->m || self->n != other->n) return false;                                           \
        array_type##_mul_array_times_scalar(self->values, other->values, v, self->m * self->n);                 \
        return true;                                                                                \
    }                                                                                \
                                                                                \
    static inline void name##_add(name##_t *self, type value) {                                                 \
        array_type##_add(self->values, self->m * self->n, value);                                               \
    }                                                                                \
                                                                                \
                                                                                \
    static inline bool name##_add_matrix(name##_t *self, name##_t *other) {                                     \
        if (self->m != other->m || self->n != other->n) return false;                                           \
        array_type##_add_array(self->values, other->values, self->m * self->n);                                 \
        return true;                                                                                \
    }                                                                                \
                                                                                \
    static inline bool name##_add_matrix_times_scalar(name##_t *self, name##_t *other, type v) {                \
        if (self->m != other->m || self->n != other->n) return false;                                           \
        array_type##_add_array_times_scalar(self->values, other->values, v, self->m * self->n);                 \
        return true;                                                                                \
    }                                                                                \
                                                                                \
    static inline void name##_sub(name##_t *self, type value) {                                                 \
        array_type##_sub(self->values, value, self->m * self->n);                                               \
    }                                                                                \
                                                                                \
    static inline bool name##_sub_matrix(name##_t *self, name##_t *other) {                                     \
        if (self->m != other->m || self->n != other->n) return false;                                           \
        array_type##_sub_array(self->values, other->values, self->m * self->n);                                 \
        return true;                                                                                \
    }                                                                                \
                                                                                \
    static inline bool name##_sub_matrix_times_scalar(name##_t *self, name##_t *other, type v) {                \
        if (self->m != other->m || self->n != other->n) return false;                                           \
        array_type##_sub_array_times_scalar(self->values, other->values, v, self->m * self->n);                 \
        return true;                                                                                \
    }                                                                                \
                                                                                \
    static name##_t *name##_read(FILE *f) {                                                                     \
        name##_t *mat = malloc(sizeof(name##_t));                                                               \
        if (mat == NULL) return NULL;                                                                           \
                                                                                \
        mat->values = NULL;                                                                                \
                                                                                \
        uint64_t m = 0;                                                                                \
        uint64_t n = 0;                                                                                \
                                                                                \
        if (!file_read_uint64(f, &m) ||                                                                         \
            !file_read_uint64(f, &n)) {                                                                         \
            goto exit_##name##_allocated;                                                                       \
        }                                                                                \
                                                                                \
        mat->m = (size_t)m;                                                                                \
        mat->n = (size_t)n;                                                                                \
                                                                                \
        size_t len_data = mat->m * mat->n;                                                                      \
                                                                                \
        type *data = malloc(len_data * sizeof(type));                                                           \
        if (data == NULL) {                                                                                \
            log_error("error in data malloc\n");                                                                \
            goto exit_##name##_allocated;                                                                       \
        }                                                                                \
                                                                                \
        if (!file_read_##array_type(f, data, len_data)) {                                                       \
            free(data);                                                                                \
            goto exit_##name##_allocated;                                                                       \
        }                                                                                \
                                                                                \
        mat->values = data;                                                                                \
                                                                                \
        return mat;                                                                                \
                                                                                \
    exit_##name##_allocated:                                                                                \
        name##_destroy(mat);                                                                                \
        return NULL;                                                                                \
    }                                                                                \
                                                                                \
    static bool name##_write(name##_t *self, FILE *f) {                                                         \
        if (self == NULL || self->values == NULL) {                                                             \
            return false;                                                                                \
        }                                                                                \
                                                                                \
        if (!file_write_uint64(f, (uint64_t)self->m) ||                                                         \
            !file_write_uint64(f, (uint64_t)self->n)) {                                                         \
            return false;                                                                                \
        }                                                                                \
                                                                                \
        uint64_t len_data = (uint64_t)self->m * (uint64_t)self->n;                                              \
                                                                                \
        for (uint64_t i = 0; i < len_data; i++) {                                                               \
            if (!file_write_##type_name(f, self->values[i])) {                                                  \
                return false;                                                                                \
            }                                                                                \
        }                                                                                \
                                                                                \
        return true;                                                                                \
    }

#define MATRIX_INIT_FLOAT_BASE(name, type, type_name, array_type)                       \
    MATRIX_INIT(name, type, type_name, array_type)                                      \
                                                                                \
    static inline void name##_log(name##_t *self) {                                     \
        array_type##_log(self->values, self->m * self->n);                              \
    }                                                                                \
                                                                                \
    static inline void name##_exp(name##_t *self) {                                     \
        array_type##_exp(self->values, self->m * self->n);                              \
    }                                                                                \
                                                                                \
    static inline void name##_dot_vector(name##_t *self, type *vec, type *result) {     \
        type *values = self->values;                                                    \
        size_t m = self->m;                                                             \
        size_t n = self->n;                                                             \
        for (size_t i = 0; i < m; i++) {                                                \
            for (size_t j = 0; j < n; j++) {                                            \
                result[i] += values[n * i + j] * vec[j];                                \
            }                                                                           \
        }                                                                               \
    }

#ifdef HAVE_CBLAS
#define MATRIX_INIT_FLOAT(name, type, type_name, array_type, blas_prefix)                                   \
    MATRIX_INIT_FLOAT_BASE(name, type, type_name, array_type)                                               \
                                                                                \
    static inline bool name##_dot_matrix(name##_t *m1, name##_t *m2, name##_t *result) {                    \
        if (m1->n != m2->m || m1->m != result->m || m2->n != result->n) {                                   \
            return false;                                                                                \
        }                                                                                \
                                                                                \
        log_debug("doing CBLAS\n");                                                                         \
        cblas_##blas_prefix##gemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,                                \
                    m1->m, m2->n, m1->n, 1.0,                                                               \
                    m1->values, m1->n,                                                                      \
                    m2->values, m2->n, 0.0,                                                                 \
                    result->values, result->n                                                               \
                    );                                                                                \
                                                                                \
        return true;                                                                                \
    }

#else
#define MATRIX_INIT_FLOAT(name, type, type_name, array_type, blas_prefix)                                       \
    MATRIX_INIT_FLOAT_BASE(name, type, type_name, array_type)                                                   \
                                                                                \
    static inline bool name##_dot_matrix(name##_t *m1, name##_t *m2, name##_t *result) {                        \
        if (m1->n != m2->m || m1->m != result->m || m2->n != result->n) {                                       \
            return false;                                                                                \
        }                                                                                \
                                                                                \
        size_t m1_rows = m1->m;                                                                                \
        size_t m1_cols = m1->n;                                                                                \
        size_t m2_rows = m2->m;                                                                                \
        size_t m2_cols = m2->n;                                                                                \
                                                                                \
        type *m1_values = m1->values;                                                                           \
        type *m2_values = m2->values;                                                                           \
        type *result_values = result->values;                                                                   \
                                                                                \
        for (size_t i = 0; i < m1_rows; i++) {                                                                  \
            for (size_t j = 0; j < m2_cols; j++) {                                                              \
                size_t result_index = m2_cols * i + j;                                                          \
                result_values[result_index] = 0.0;                                                              \
                for (size_t k = 0; k < m2_rows; k++) {                                                          \
                    result_values[result_index] += m1_values[m1_cols * i + k] * m2_values[m2_cols * k + j];     \
                }                                                                                \
            }                                                                                \
        }                                                                                \
                                                                                \
        return true;                                                                                \
    }
#endif

MATRIX_INIT(uint32_matrix, uint32_t, uint32, uint32_array)

MATRIX_INIT_FLOAT(float_matrix, float, float, float_array,s)
MATRIX_INIT_FLOAT(double_matrix, double, double, double_array,d)

#endif