/* rng/taus113.c
 * Copyright (C) 2002 Atakan Gurkan
 * Based on the file taus.c which has the notice
 * Copyright (C) 1996, 1997, 1998, 1999, 2000, 2007 James Theiler, Brian Gough
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* This is a maximally equidistributed combined, collision free
   Tausworthe generator, with a period ~2^{113}. The sequence is,

   x_n = (z1_n ^ z2_n ^ z3_n ^ z4_n)

   b = (((z1_n <<  6) ^ z1_n) >> 13)
   z1_{n+1} = (((z1_n & 4294967294) << 18) ^ b)
   b = (((z2_n <<  2) ^ z2_n) >> 27)
   z2_{n+1} = (((z2_n & 4294967288) <<  2) ^ b)
   b = (((z3_n << 13) ^ z3_n) >> 21)
   z3_{n+1} = (((z3_n & 4294967280) <<  7) ^ b)
   b = (((z4_n <<  3)  ^ z4_n) >> 12)
   z4_{n+1} = (((z4_n & 4294967168) << 13) ^ b)

   computed modulo 2^32. In the formulas above '^' means exclusive-or
   (C-notation), not exponentiation.
   The algorithm is for 32-bit integers, hence a bitmask is used to clear
   all but least significant 32 bits, after left shifts, to make the code
   work on architectures where integers are 64-bit.

   The generator is initialized with
   zi = (69069 * z{i+1}) MOD 2^32 where z0 is the seed provided
   During initialization a check is done to make sure that the initial seeds
   have a required number of their most significant bits set.
   After this, the state is passed through the RNG 10 times to ensure the
   state satisfies a recurrence relation.

   References:
   P. L'Ecuyer, "Tables of Maximally-Equidistributed Combined LFSR Generators",
   Mathematics of Computation, 68, 225 (1999), 261--269.
     http://www.iro.umontreal.ca/~lecuyer/myftp/papers/tausme2.ps
   P. L'Ecuyer, "Maximally Equidistributed Combined Tausworthe Generators",
   Mathematics of Computation, 65, 213 (1996), 203--213.
     http://www.iro.umontreal.ca/~lecuyer/myftp/papers/tausme.ps
   the online version of the latter contains corrections to the print version.
*/

#include <config.h>
#include <stdlib.h>
#include <gsl/gsl_rng.h>

#define LCG(n) ((69069UL * n) & 0xffffffffUL)
#define MASK 0xffffffffUL

static inline unsigned long int taus113_get (void *vstate);
static double taus113_get_double (void *vstate);
static void taus113_set (void *state, unsigned long int s);

typedef struct
{
  unsigned long int z1, z2, z3, z4;
}
taus113_state_t;

static inline unsigned long
taus113_get (void *vstate)
{
  taus113_state_t *state = (taus113_state_t *) vstate;
  unsigned long b1, b2, b3, b4;

  b1 = ((((state->z1 << 6UL) & MASK) ^ state->z1) >> 13UL);
  state->z1 = ((((state->z1 & 4294967294UL) << 18UL) & MASK) ^ b1);

  b2 = ((((state->z2 << 2UL) & MASK) ^ state->z2) >> 27UL);
  state->z2 = ((((state->z2 & 4294967288UL) << 2UL) & MASK) ^ b2);

  b3 = ((((state->z3 << 13UL) & MASK) ^ state->z3) >> 21UL);
  state->z3 = ((((state->z3 & 4294967280UL) << 7UL) & MASK) ^ b3);

  b4 = ((((state->z4 << 3UL) & MASK) ^ state->z4) >> 12UL);
  state->z4 = ((((state->z4 & 4294967168UL) << 13UL) & MASK) ^ b4);

  return (state->z1 ^ state->z2 ^ state->z3 ^ state->z4);

}

static double
taus113_get_double (void *vstate)
{
  return taus113_get (vstate) / 4294967296.0;
}

static void
taus113_set (void *vstate, unsigned long int s)
{
  taus113_state_t *state = (taus113_state_t *) vstate;

  if (!s)
    s = 1UL;                    /* default seed is 1 */

  state->z1 = LCG (s);
  if (state->z1 < 2UL)
    state->z1 += 2UL;
  state->z2 = LCG (state->z1);
  if (state->z2 < 8UL)
    state->z2 += 8UL;
  state->z3 = LCG (state->z2);
  if (state->z3 < 16UL)
    state->z3 += 16UL;
  state->z4 = LCG (state->z3);
  if (state->z4 < 128UL)
    state->z4 += 128UL;

  /* Calling RNG ten times to satify recurrence condition */
  taus113_get (state);
  taus113_get (state);
  taus113_get (state);
  taus113_get (state);
  taus113_get (state);
  taus113_get (state);
  taus113_get (state);
  taus113_get (state);
  taus113_get (state);
  taus113_get (state);

  return;
}

static const gsl_rng_type taus113_type = {
  "taus113",                    /* name */
  0xffffffffUL,                 /* RAND_MAX */
  0,                            /* RAND_MIN */
  sizeof (taus113_state_t),
  &taus113_set,
  &taus113_get,
  &taus113_get_double
};

const gsl_rng_type *gsl_rng_taus113 = &taus113_type;

/*  Rules for analytic calculations using GNU Emacs Calc:
    (used to find the values for the test program)

  [ LCG(n) := n * 69069 mod (2^32) ]

  [ b1(x) := rsh(xor(lsh(x, 6), x), 13),
  q1(x) := xor(lsh(and(x, 4294967294), 18), b1(x)),
  b2(x) := rsh(xor(lsh(x, 2), x), 27),
  q2(x) := xor(lsh(and(x, 4294967288), 2), b2(x)),
  b3(x) := rsh(xor(lsh(x, 13), x), 21),
  q3(x) := xor(lsh(and(x, 4294967280), 7), b3(x)),
  b4(x) := rsh(xor(lsh(x, 3), x), 12),
  q4(x) := xor(lsh(and(x, 4294967168), 13), b4(x))
  ]

  [ S([z1,z2,z3,z4]) := [q1(z1), q2(z2), q3(z3), q4(z4)] ]
*/


/*
 * Copyright 2021 The DAPHNE Consortium
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef SRC_RUNTIME_LOCAL_KERNELS_SYRK_H
#define SRC_RUNTIME_LOCAL_KERNELS_SYRK_H

#include <runtime/local/context/DaphneContext.h>
#include <runtime/local/datastructures/CSRMatrix.h>
#include <runtime/local/datastructures/DataObjectFactory.h>
#include <runtime/local/datastructures/DenseMatrix.h>

#include <cblas.h>

// ****************************************************************************
// Struct for partial template specialization
// ****************************************************************************

template<class DTRes, class DTArg>
struct Syrk {
    static void apply(DTRes *& res, const DTArg * arg, DCTX(ctx)) = delete;
};

// ****************************************************************************
// Convenience function
// ****************************************************************************

template<class DTRes, class DTArg>
void syrk(DTRes *& res, const DTArg * arg, DCTX(ctx)) {
    Syrk<DTRes, DTArg>::apply(res, arg, ctx);
}

// ****************************************************************************
// (Partial) template specializations for different data/value types
// ****************************************************************************

// ----------------------------------------------------------------------------
// DenseMatrix <- DenseMatrix
// ----------------------------------------------------------------------------

template<>
struct Syrk<DenseMatrix<double>, DenseMatrix<double>> {
    static void apply(DenseMatrix<double> *& res, const DenseMatrix<double> * arg, DCTX(ctx)) {
        const size_t numRows = arg->getNumRows();
        const size_t numCols = arg->getNumCols();

        if(res == nullptr)
            res = DataObjectFactory::create<DenseMatrix<double>>(numCols, numCols, false);

        cblas_dsyrk(CblasRowMajor,
            CblasUpper,
            CblasTrans,
            numCols,
            numRows,
            1.0,
            arg->getValues(),
            arg->getRowSkip(),
            0.0,
            res->getValues(),
            res->getRowSkip());
        for (auto r = 0u; r < numCols; ++r) {
            for (auto c = r + 1; c < numCols; ++c) {
                res->set(c, r, res->get(r, c));
            }
        }
    }
};

template<>
struct Syrk<DenseMatrix<float>, DenseMatrix<float>> {
    static void apply(DenseMatrix<float> *& res, const DenseMatrix<float> * arg, DCTX(ctx)) {
        const size_t numRows = arg->getNumRows();
        const size_t numCols = arg->getNumCols();

        if(res == nullptr)
            res = DataObjectFactory::create<DenseMatrix<float>>(numCols, numCols, false);

        cblas_ssyrk(CblasRowMajor,
            CblasUpper,
            CblasTrans,
            numCols,
            numRows,
            1.0,
            arg->getValues(),
            arg->getRowSkip(),
            0.0,
            res->getValues(),
            res->getRowSkip());
        for (auto r = 0u; r < numCols; ++r) {
            for (auto c = r + 1; c < numCols; ++c) {
                res->set(c, r, res->get(r, c));
            }
        }
    }
};

// ----------------------------------------------------------------------------
// CSRMatrix <- CSRMatrix
// ----------------------------------------------------------------------------

template<typename VT>
struct Syrk<CSRMatrix<VT>, CSRMatrix<VT>> {
    static void apply(CSRMatrix<VT> *& res, const CSRMatrix<VT> * arg, DCTX(ctx)) {
        const size_t numRows = arg->getNumRows();
        const size_t numCols = arg->getNumCols();

        if(res == nullptr)
            res = DataObjectFactory::create<CSRMatrix<VT>>(numCols, numRows, arg->getNumNonZeros(), false);
        assert(false && "TODO: Syrk for Sparse");
    }
};

#endif //SRC_RUNTIME_LOCAL_KERNELS_SYRK_H


/* multimin/gsl_multimin.h
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Fabrice Rossi
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* Modified by Tuomo Keskitalo to include fminimizer and
   Nelder Mead related lines */

#ifndef __GSL_MULTIMIN_H__
#define __GSL_MULTIMIN_H__

#if !defined( GSL_FUN )
#  if !defined( GSL_DLL )
#    define GSL_FUN extern
#  elif defined( BUILD_GSL_DLL )
#    define GSL_FUN extern __declspec(dllexport)
#  else
#    define GSL_FUN extern __declspec(dllimport)
#  endif
#endif

#include <stdlib.h>
#include <gsl/gsl_types.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_vector.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_min.h>

#undef __BEGIN_DECLS
#undef __END_DECLS
#ifdef __cplusplus
# define __BEGIN_DECLS extern "C" {
# define __END_DECLS }
#else
# define __BEGIN_DECLS /* empty */
# define __END_DECLS /* empty */
#endif

__BEGIN_DECLS

/* Definition of an arbitrary real-valued function with gsl_vector input and */
/* parameters */
struct gsl_multimin_function_struct
{
  double (* f) (const gsl_vector * x, void * params);
  size_t n;
  void * params;
};

typedef struct gsl_multimin_function_struct gsl_multimin_function;

#define GSL_MULTIMIN_FN_EVAL(F,x) (*((F)->f))(x,(F)->params)

/* Definition of an arbitrary differentiable real-valued function */
/* with gsl_vector input and parameters */
struct gsl_multimin_function_fdf_struct
{
  double (* f) (const gsl_vector  * x, void * params);
  void (* df) (const gsl_vector * x, void * params,gsl_vector * df);
  void (* fdf) (const gsl_vector * x, void * params,double *f,gsl_vector * df);
  size_t n;
  void * params;
};

typedef struct gsl_multimin_function_fdf_struct gsl_multimin_function_fdf;

#define GSL_MULTIMIN_FN_EVAL_F(F,x) (*((F)->f))(x,(F)->params)
#define GSL_MULTIMIN_FN_EVAL_DF(F,x,g) (*((F)->df))(x,(F)->params,(g))
#define GSL_MULTIMIN_FN_EVAL_F_DF(F,x,y,g) (*((F)->fdf))(x,(F)->params,(y),(g))

GSL_FUN int gsl_multimin_diff (const gsl_multimin_function * f,
                       const gsl_vector * x, gsl_vector * g);

/* minimization of non-differentiable functions */

typedef struct
{
  const char *name;
  size_t size;
  int (*alloc) (void *state, size_t n);
  int (*set) (void *state, gsl_multimin_function * f,
              const gsl_vector * x,
              double * size,
              const gsl_vector * step_size);
  int (*iterate) (void *state, gsl_multimin_function * f,
                  gsl_vector * x,
                  double * size,
                  double * fval);
  void (*free) (void *state);
}
gsl_multimin_fminimizer_type;

typedef struct
{
  /* multi dimensional part */
  const gsl_multimin_fminimizer_type *type;
  gsl_multimin_function *f;

  double fval;
  gsl_vector * x;

  double size;

  void *state;
}
gsl_multimin_fminimizer;

GSL_FUN gsl_multimin_fminimizer *
gsl_multimin_fminimizer_alloc(const gsl_multimin_fminimizer_type *T,
                              size_t n);

GSL_FUN int
gsl_multimin_fminimizer_set (gsl_multimin_fminimizer * s,
                             gsl_multimin_function * f,
                             const gsl_vector * x,
                             const gsl_vector * step_size);

GSL_FUN void
gsl_multimin_fminimizer_free(gsl_multimin_fminimizer *s);

GSL_FUN const char *
gsl_multimin_fminimizer_name (const gsl_multimin_fminimizer * s);

GSL_FUN int
gsl_multimin_fminimizer_iterate(gsl_multimin_fminimizer *s);

GSL_FUN gsl_vector *
gsl_multimin_fminimizer_x (const gsl_multimin_fminimizer * s);

GSL_FUN double
gsl_multimin_fminimizer_minimum (const gsl_multimin_fminimizer * s);

GSL_FUN double
gsl_multimin_fminimizer_size (const gsl_multimin_fminimizer * s);

/* Convergence test functions */

GSL_FUN int
gsl_multimin_test_gradient(const gsl_vector * g,double epsabs);

GSL_FUN int
gsl_multimin_test_size(const double size ,double epsabs);

/* minimisation of differentiable functions */

typedef struct
{
  const char *name;
  size_t size;
  int (*alloc) (void *state, size_t n);
  int (*set) (void *state, gsl_multimin_function_fdf * fdf,
              const gsl_vector * x, double * f,
              gsl_vector * gradient, double step_size, double tol);
  int (*iterate) (void *state,gsl_multimin_function_fdf * fdf,
                  gsl_vector * x, double * f,
                  gsl_vector * gradient, gsl_vector * dx);
  int (*restart) (void *state);
  void (*free) (void *state);
}
gsl_multimin_fdfminimizer_type;

typedef struct
{
  /* multi dimensional part */
  const gsl_multimin_fdfminimizer_type *type;
  gsl_multimin_function_fdf *fdf;

  double f;
  gsl_vector * x;
  gsl_vector * gradient;
  gsl_vector * dx;

  void *state;
}
gsl_multimin_fdfminimizer;

GSL_FUN gsl_multimin_fdfminimizer *
gsl_multimin_fdfminimizer_alloc(const gsl_multimin_fdfminimizer_type *T,
                                size_t n);

GSL_FUN int
gsl_multimin_fdfminimizer_set (gsl_multimin_fdfminimizer * s,
                               gsl_multimin_function_fdf *fdf,
                               const gsl_vector * x,
                               double step_size, double tol);

GSL_FUN void
gsl_multimin_fdfminimizer_free(gsl_multimin_fdfminimizer *s);

GSL_FUN const char *
gsl_multimin_fdfminimizer_name (const gsl_multimin_fdfminimizer * s);

GSL_FUN int
gsl_multimin_fdfminimizer_iterate(gsl_multimin_fdfminimizer *s);

GSL_FUN int
gsl_multimin_fdfminimizer_restart(gsl_multimin_fdfminimizer *s);

GSL_FUN gsl_vector *
gsl_multimin_fdfminimizer_x (const gsl_multimin_fdfminimizer * s);

GSL_FUN gsl_vector *
gsl_multimin_fdfminimizer_dx (const gsl_multimin_fdfminimizer * s);

GSL_FUN gsl_vector *
gsl_multimin_fdfminimizer_gradient (const gsl_multimin_fdfminimizer * s);

GSL_FUN double
gsl_multimin_fdfminimizer_minimum (const gsl_multimin_fdfminimizer * s);

GSL_VAR const gsl_multimin_fdfminimizer_type *gsl_multimin_fdfminimizer_steepest_descent;
GSL_VAR const gsl_multimin_fdfminimizer_type *gsl_multimin_fdfminimizer_conjugate_pr;
GSL_VAR const gsl_multimin_fdfminimizer_type *gsl_multimin_fdfminimizer_conjugate_fr;
GSL_VAR const gsl_multimin_fdfminimizer_type *gsl_multimin_fdfminimizer_vector_bfgs;
GSL_VAR const gsl_multimin_fdfminimizer_type *gsl_multimin_fdfminimizer_vector_bfgs2;
GSL_VAR const gsl_multimin_fminimizer_type *gsl_multimin_fminimizer_nmsimplex;
GSL_VAR const gsl_multimin_fminimizer_type *gsl_multimin_fminimizer_nmsimplex2;

__END_DECLS

#endif /* __GSL_MULTIMIN_H__ */


/* rng/r250.c
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 James Theiler, Brian Gough
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <config.h>
#include <stdlib.h>
#include <gsl/gsl_rng.h>

/* This is a shift-register random number generator. The sequence is

   x_n = x_{n-103} ^ x_{n-250}        ("^" means XOR)

   defined on 32-bit words.

   The first 250 elements x_1 .. x_250 are first initialized as x_n =
   s_n, where s_n = (69069*s_{n-1}) mod 2^32 and s_0=s is the
   user-supplied seed. To ensure that the sequence does not lie on a
   subspace we force 32 of the entries to be linearly independent.  We
   take the 32 elements x[3], x[10], x[17], x[24], ..., 213 and apply
   the following operations,

   x[3]   &= 11111111111111111111111111111111
   x[3]   |= 10000000000000000000000000000000
   x[10]  &= 01111111111111111111111111111111
   x[10]  |= 01000000000000000000000000000000
   x[17]  &= 00111111111111111111111111111111
   x[17]  |= 00100000000000000000000000000000
   ....      ...
   x[206] &= 00000000000000000000000000000111
   x[206] |= 00000000000000000000000000000100
   x[213] &= 00000000000000000000000000000011
   x[213] |= 00000000000000000000000000000010
   x[220] &= 00000000000000000000000000000001
   x[220] |= 00000000000000000000000000000001

   i.e. if we consider the bits of the 32 elements as forming a 32x32
   array then we are setting the diagonal bits of the array to one and
   masking the lower triangle below the diagonal to zero.

   With this initialization procedure the theoretical value of
   x_{10001} is 1100653588 for s = 1 (Actually I got this by running
   the original code). The subscript 10001 means (1) seed the
   generator with s = 1 and then do 10000 actual iterations.

   The period of this generator is about 2^250.

   The algorithm works for any number of bits. It is implemented here
   for 32 bits.

   From: S. Kirkpatrick and E. Stoll, "A very fast shift-register
   sequence random number generator", Journal of Computational Physics,
   40, 517-526 (1981). */

static inline unsigned long int r250_get (void *vstate);
static double r250_get_double (void *vstate);
static void r250_set (void *state, unsigned long int s);

typedef struct
  {
    int i;
    unsigned long x[250];
  }
r250_state_t;

static inline unsigned long int
r250_get (void *vstate)
{
  r250_state_t *state = (r250_state_t *) vstate;
  unsigned long int k;
  int j;

  int i = state->i;

  if (i >= 147)
    {
      j = i - 147;
    }
  else
    {
      j = i + 103;
    }

  k = state->x[i] ^ state->x[j];
  state->x[i] = k;

  if (i >= 249)
    {
      state->i = 0;
    }
  else
    {
      state->i = i + 1;
    }

  return k;
}

static double
r250_get_double (void *vstate)
{
  return r250_get (vstate) /  4294967296.0 ;
}

static void
r250_set (void *vstate, unsigned long int s)
{
  r250_state_t *state = (r250_state_t *) vstate;

  int i;

  if (s == 0)
    s = 1;	/* default seed is 1 */

  state->i = 0;

#define LCG(n) ((69069 * n) & 0xffffffffUL)

  for (i = 0; i < 250; i++)	/* Fill the buffer  */
    {
      s = LCG (s);
      state->x[i] = s;
    }

  {
    /* Masks for turning on the diagonal bit and turning off the
       leftmost bits */

    unsigned long int msb = 0x80000000UL;
    unsigned long int mask = 0xffffffffUL;

    for (i = 0; i < 32; i++)
      {
	int k = 7 * i + 3;	/* Select a word to operate on        */
	state->x[k] &= mask;	/* Turn off bits left of the diagonal */
	state->x[k] |= msb;	/* Turn on the diagonal bit           */
	mask >>= 1;
	msb >>= 1;
      }
  }

  return;
}

static const gsl_rng_type r250_type =
{"r250",			/* name */
 0xffffffffUL,			/* RAND_MAX */
 0,			        /* RAND_MIN */
 sizeof (r250_state_t),
 &r250_set,
 &r250_get,
 &r250_get_double};

const gsl_rng_type *gsl_rng_r250 = &r250_type;


/*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*
**                                                                                **
** This file forms part of the Underworld geophysics modelling application.         **
**                                                                                **
** For full license and copyright information, please refer to the LICENSE.md file  **
** located at the project root, or contact the authors.                             **
**                                                                                **
**~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*/
/*

Given a block system
A x = b,
or
( K G )(u) = (f)
( D C )(p)   (h)

We define a symmetrically scaled system,
L A R R^-1 x = L b,		A'x' = b'
where L = diag(L1, L2), R = diag(R1,R2) are block diagonal
and L_i and R_i are both diagonal matrirces.

The scaling produces
( L1   )( K G )( R1   )( R1   )^-1(u) = ( L1   )(f)
(   L2 )( D C )(   R2 )(   R2 )   (p)   (   L2 )(h)
and we solve
( L1   )( K G )( R1   )(v) = ( L1   )(f)
(   L2 )( D C )(   R2 )(q)   (   L2 )(h)
or
( L1KR1 L1GR2 )(v) = (L1f)
( L2DR1 L2CR2 )(q)   (L2h)

The solution u,p is recovered via
(u) = ( R1   )(v)
(p)   (   R2 )(q)

*/

#include <petsc.h>
#include <petscmat.h>
#include <petscvec.h>

#include <StGermain/StGermain.h>
#include <StgDomain/StgDomain.h>

#include "common-driver-utils.h"
#include "stokes_block_scaling.h"

/* private prototypes */
PetscErrorCode BSSCR_MatStokesBlock_ApplyScaling( MatStokesBlockScaling BA, Mat A, Vec b, Vec x, Mat S, PetscTruth sym );

/* A x = b -> A'x' = b' */
/*
Note this routine actually modifies the matrix and rhs b.
*/

// updated
PetscErrorCode BSSCR_MatStokesBlock_ApplyScaling( MatStokesBlockScaling BA, Mat A, Vec b, Vec x, Mat S, PetscTruth sym )
{
	Mat K,G,D,C;
	Vec L1,L2, R1,R2;
	Vec f,h, u,p;

	/* Get the scalings out the block mat data */
	VecNestGetSubVec( BA->Lz, 0, &L1 );
	VecNestGetSubVec( BA->Lz, 1, &L2 );
	VecNestGetSubVec( BA->Rz, 0, &R1 );
	VecNestGetSubVec( BA->Rz, 1, &R2 );

	/* get the subblock solution and rhs */
	if( x != PETSC_NULL ) {
		VecNestGetSubVec( x, 0, &u );
		VecNestGetSubVec( x, 1, &p );

		VecPointwiseDivide( u, u,R1); /* x <- x * 1/R1 */
		VecPointwiseDivide( p, p,R2);

	}
	if( b != PETSC_NULL ) {
		VecNestGetSubVec( b, 0, &f );
		VecNestGetSubVec( b, 1, &h );

		VecPointwiseMult( f, f,L1); /* f <- f * L1 */
		VecPointwiseMult( h, h,L2);

	}

	/* Scale matrices */
	MatNestGetSubMat( A, 0,0, &K );
	MatNestGetSubMat( A, 0,1, &G );
	MatNestGetSubMat( A, 1,0, &D );
	MatNestGetSubMat( A, 1,1, &C );

	if( K != PETSC_NULL ) {		MatDiagonalScale( K, L1,R1 );		}
	if( G != PETSC_NULL ) {		MatDiagonalScale( G, L1,R2 );		}
	if( D != PETSC_NULL && !sym ) {	MatDiagonalScale( D, L2,R1 );		}
	if( C != PETSC_NULL ) {		MatDiagonalScale( C, L2,R2 );		}
	if( S != PETSC_NULL ) {		MatDiagonalScale( S, L2,R2 );		}

	PetscFunctionReturn(0);
}
PetscErrorCode BSSCR_MatStokesBlock_ApplyScaling2( MatStokesBlockScaling BA, Mat A, Vec b, Vec x, Mat S, Mat M, PetscTruth sym )
{
	Mat K,G,D,C;
	Vec L1,L2, R1,R2;
	Vec f,h, u,p;

	/* Get the scalings out the block mat data */
	VecNestGetSubVec( BA->Lz, 0, &L1 );
	VecNestGetSubVec( BA->Lz, 1, &L2 );
	VecNestGetSubVec( BA->Rz, 0, &R1 );
	VecNestGetSubVec( BA->Rz, 1, &R2 );

	/* get the subblock solution and rhs */
	if( x != PETSC_NULL ) {
		VecNestGetSubVec( x, 0, &u );
		VecNestGetSubVec( x, 1, &p );

		VecPointwiseDivide( u, u,R1); /* x <- x * 1/R1 */
		VecPointwiseDivide( p, p,R2);

	}
	if( b != PETSC_NULL ) {
		VecNestGetSubVec( b, 0, &f );
		VecNestGetSubVec( b, 1, &h );

		VecPointwiseMult( f, f,L1); /* f <- f * L1 */
		VecPointwiseMult( h, h,L2);

	}

	/* Scale matrices */
	MatNestGetSubMat( A, 0,0, &K );
	MatNestGetSubMat( A, 0,1, &G );
	MatNestGetSubMat( A, 1,0, &D );
	MatNestGetSubMat( A, 1,1, &C );

	if( K != PETSC_NULL ) {		MatDiagonalScale( K, L1,R1 );		}
	if( G != PETSC_NULL ) {		MatDiagonalScale( G, L1,R2 );		}
	if( D != PETSC_NULL && !sym ) {	MatDiagonalScale( D, L2,R1 );		}
	if( C != PETSC_NULL ) {		MatDiagonalScale( C, L2,R2 );		}
	if( S != PETSC_NULL ) {		MatDiagonalScale( S, L2,R2 );		}
	if( M != PETSC_NULL ) {		MatDiagonalScale( M, L2,R2 );		}

	PetscFunctionReturn(0);
}

// updated
PetscErrorCode BSSCR_MatStokesBlockScalingCreate( MatStokesBlockScaling *_BA )
{
	MatStokesBlockScaling BA;

	PetscMalloc( sizeof(struct _p_MatStokesBlockScaling), &BA );

	BA->Lz = PETSC_NULL;
	BA->Rz = PETSC_NULL;

	BA->scaling_exists              = PETSC_FALSE;
	BA->scalings_have_been_inverted = PETSC_FALSE;
	BA->system_has_been_scaled      = PETSC_FALSE;

	*_BA = BA;

	PetscFunctionReturn(0);
}

// updated
PetscErrorCode BSSCR_MatStokesBlockScalingDestroy( MatStokesBlockScaling BA )
{

	if( BA->scaling_exists == PETSC_FALSE ) PetscFunctionReturn(0);

	if( BA->Lz != PETSC_NULL ) {
		Stg_VecDestroy(&BA->Lz );
		BA->Lz = PETSC_NULL;
	}

	if( BA->Rz != PETSC_NULL ) {
		Stg_VecDestroy(&BA->Rz );
		BA->Rz = PETSC_NULL;
	}

	PetscFree( BA );

	PetscFunctionReturn(0);
}

/*
A is 2x2 block matrix
b and x are 2x1 block vectors
*/
// updated
PetscErrorCode BSSCR_MatBlock_ConstructScaling( MatStokesBlockScaling BA, Mat A, Vec b, Vec x )
{
    if( BA->scaling_exists == PETSC_FALSE ) {

	VecDuplicate( x, &BA->Lz );
	VecDuplicate( x, &BA->Rz );

	BA->scaling_exists = PETSC_TRUE;
	BSSCR_MatStokesBlockDefaultBuildScaling( BA,A );
	BA->scalings_have_been_inverted = PETSC_FALSE;
    }

    PetscFunctionReturn(0);
}

// updated
PetscErrorCode BSSCR_mat_block_invert_scalings( MatStokesBlockScaling BA )
{
	Vec L1,L2, R1,R2;

	VecNestGetSubVec( BA->Lz, 0, &L1 );
	VecNestGetSubVec( BA->Lz, 1, &L2 );
	VecNestGetSubVec( BA->Rz, 0, &R1 );
	VecNestGetSubVec( BA->Rz, 1, &R2 );

	VecReciprocal(L1);
	VecReciprocal(L2);
	VecReciprocal(R1);
	VecReciprocal(R2);

	/* toggle inversion flag */
	if( BA->scalings_have_been_inverted == PETSC_TRUE ) {   BA->scalings_have_been_inverted = PETSC_FALSE;  }
	if( BA->scalings_have_been_inverted == PETSC_FALSE ) {  BA->scalings_have_been_inverted = PETSC_TRUE;   }

	PetscFunctionReturn(0);
}

/* updated */
PetscErrorCode BSSCR_MatStokesBlockScaleSystem( MatStokesBlockScaling BA, Mat A, Vec b, Vec x, Mat S, PetscTruth sym )
{
	if( BA->scaling_exists == PETSC_FALSE ) {
		BSSCR_MatBlock_ConstructScaling( BA,A,b,x );
	}

	if( BA->scalings_have_been_inverted == PETSC_TRUE ) {
		BSSCR_mat_block_invert_scalings(BA); /* to undo inversion */
	}

	BSSCR_MatStokesBlock_ApplyScaling(BA,A,b,x,S,sym);
	BA->system_has_been_scaled = PETSC_TRUE;

	/*
	PetscPrintf( PETSC_COMM_WORLD, "Post Scaling \n");
	MatBlock_ReportOperatorScales(A);
	*/

	PetscFunctionReturn(0);
}
PetscErrorCode BSSCR_MatStokesBlockScaleSystem2( MatStokesBlockScaling BA, Mat A, Vec b, Vec x, Mat S, Mat M, PetscTruth sym )
{
	if( BA->scaling_exists == PETSC_FALSE ) {
		BSSCR_MatBlock_ConstructScaling( BA,A,b,x );
	}

	if( BA->scalings_have_been_inverted == PETSC_TRUE ) {
		BSSCR_mat_block_invert_scalings(BA); /* to undo inversion */
	}

	BSSCR_MatStokesBlock_ApplyScaling2(BA,A,b,x,S,M,sym);
	BA->system_has_been_scaled = PETSC_TRUE;

	/*
	PetscPrintf( PETSC_COMM_WORLD, "Post Scaling \n");
	MatBlock_ReportOperatorScales(A);
	*/

	PetscFunctionReturn(0);
}

// updated
PetscErrorCode BSSCR_MatStokesBlockUnScaleSystem( MatStokesBlockScaling BA, Mat A, Vec b, Vec x, Mat S, PetscTruth sym )
{

	if( BA->system_has_been_scaled == PETSC_FALSE ) {
		printf("Warning: MatBlock has not been scaled !! \n");
		PetscFunctionReturn(0);
	}

	if( BA->scalings_have_been_inverted == PETSC_FALSE ) {
		BSSCR_mat_block_invert_scalings(BA);
	}

	BSSCR_MatStokesBlock_ApplyScaling(BA,A,b,x,S,sym);
	BA->system_has_been_scaled = PETSC_FALSE;

	PetscFunctionReturn(0);
}

PetscErrorCode BSSCR_MatStokesBlockReportOperatorScales( Mat A, PetscTruth sym )
{
	Vec rA, rG;
	PetscInt loc;
	PetscReal min, max;
	Mat K,G,D,C;

	MatNestGetSubMat( A, 0,0, &K );
	MatNestGetSubMat( A, 0,1, &G );
	MatNestGetSubMat( A, 1,0, &D );
	MatNestGetSubMat( A, 1,1, &C );

	MatGetVecs( K, PETSC_NULL, &rA );
	VecDuplicate( rA, &rG );

	/* Report the row max and mins */
	if (K!=PETSC_NULL) {
                PetscReal KNorm;
		MatGetRowMax( K, rA, PETSC_NULL );
		VecMax( rA, &loc, &max );
		PetscPrintf( PETSC_COMM_WORLD, "Sup_max(K) = %g \n", max );

		MatGetRowMinAbs( K, rA, PETSC_NULL );
		VecMin( rA, &loc, &min );
		PetscPrintf( PETSC_COMM_WORLD, "Sup_min(K) = %g \n", min );

		MatNorm(K,NORM_1,&KNorm);
		PetscPrintf( PETSC_COMM_WORLD, "norm_1(K)  = %g \n\n",KNorm);
	}

	if( G != PETSC_NULL ) {
		MatGetRowMax( G, rG, PETSC_NULL );
		VecMax( rG, &loc, &max );
		PetscPrintf( PETSC_COMM_WORLD, "Sup_max(G) = %g \n", max );

		MatGetRowMinAbs( G, rG, PETSC_NULL );
		VecMin( rG, &loc, &min );
		PetscPrintf( PETSC_COMM_WORLD, "Sup_min(G) = %g \n", min );
	}

	if( D != PETSC_NULL && !sym ) {
                Vec rD;

                MatGetVecs( D, PETSC_NULL, &rD );
		MatGetRowMax( D, rD, PETSC_NULL );
		VecMax( rD, &loc, &max );
		PetscPrintf( PETSC_COMM_WORLD, "Sup_max(D) = %g \n", max );

		MatGetRowMinAbs( D, rD, PETSC_NULL );
		VecMin( rD, &loc, &min );
		PetscPrintf( PETSC_COMM_WORLD, "Sup_min(D) = %g \n", min );

                Stg_VecDestroy(&rD );
	}

	if( C != PETSC_NULL ) {
		Vec cG;

		MatGetVecs( G, &cG, PETSC_NULL );
		MatGetRowMax( C, cG, PETSC_NULL );
		VecMax( cG, &loc, &max );
		PetscPrintf( PETSC_COMM_WORLD, "Sup_max(C) = %g \n", max );

		MatGetRowMin( C, cG, PETSC_NULL );
		VecMin( cG, &loc, &min );
		PetscPrintf( PETSC_COMM_WORLD, "Sup_min(C) = %g \n\n", min );

		Stg_VecDestroy(&cG);
	}

	Stg_VecDestroy(&rA );
	Stg_VecDestroy(&rG );

	PetscFunctionReturn(0);
}

// updated
PetscErrorCode BSSCR_MatStokesBlockDefaultBuildScaling( MatStokesBlockScaling BA, Mat A )
{
	Mat K,G,D,C;
	Vec rG;
	PetscScalar rg2, rg, ra;
	PetscInt N;
	Vec rA, rC;
	Vec L1,L2, R1,R2;

	VecNestGetSubVec( BA->Lz, 0, &L1 );
	VecNestGetSubVec( BA->Lz, 1, &L2 );

	VecNestGetSubVec( BA->Rz, 0, &R1 );
	VecNestGetSubVec( BA->Rz, 1, &R2 );

	rA = L1;
	rC = L2;

	MatNestGetSubMat( A, 0,0, &K );
	MatNestGetSubMat( A, 0,1, &G );
	MatNestGetSubMat( A, 1,0, &D );
	MatNestGetSubMat( A, 1,1, &C );

	VecDuplicate( rA, &rG );

	/* Get magnitude of K */
	//px_MatGetAbsRowSum( K, rA );
	MatGetRowMax( K, rA, PETSC_NULL );

	VecSqrt( rA );
	VecReciprocal( rA );

	VecDot( rA,rA, &ra );
	VecGetSize( rA, &N );
	ra = PetscSqrtScalar( ra/N );

	/* Get magnitude of G */
	//px_MatGetAbsRowSum( G, rG );
	MatGetRowMax( G, rG, PETSC_NULL );

	VecDot( rG, rG, &rg2 );
	VecGetSize( rG, &N );
	rg = PetscSqrtScalar(rg2/N);
	//      printf("rg = %f \n", rg );

	VecSet( rC, 1.0/(rg*ra) );

	Stg_VecDestroy(&rG );

	VecCopy( L1, R1 );
	VecCopy( L2, R2 );

	PetscFunctionReturn(0);
}


#if !defined(THDM_H)
#define THDM_H

#include "SM.h"
#include <complex>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_matrix.h>

#include "CouplingFunctionTypeI.h"
#include "CouplingFunctionTypeII.h"
#include "CouplingFunctionTypeLS.h"
#include "CouplingFunctionTypeFL.h"

using namespace std;

/**
* \mainpage 2HDMC Class documentation
* 2HDMC is a general-purpose calculator for the two-Higgs doublet model.
* It allows parametrization of the Higgs potential in many different ways,
* convenient specification of generic Yukawa sectors, the evaluation of decay
* widths, theoretical constraints and much more.
*/

/**
* @brief Specifies a general two-Higgs doublet model
*
* This class implements a description of a general two-Higgs doublet model
* in terms of the potential parameters and Yukawa couplings necessary for
* a full specification of the model. Several methods are available to set
* the model parameters to various special cases.
*
* From a THDM object, all the couplings between physical states can be
* accessed, including Higgs-fermion, Higgs-Vector, Higgs-Higgs, 3H and 4H
* couplings.
*/
//static int thdmc_set_param(int key, int npara, double *para, double *res);

int thdmc_set_param(int key, double smpara[], double para[], double res[], int slha);

typedef ComplexType (*CFunc_NLO)(double, double, double, double, double, double, double, double, double, double, SM);
ComplexType ZEROCOUPLING(double MHL2, double MHH2, double MHA2, double MHp2, double M2, double beta, double alpha, double m12, double m22, double m32, SM sm);
// For simplicity, even though SVV, SSV and SSS coupling is independent of the Yukawa type, I still use them to indicate the formula.
#define UPID 10
#define DOID 20
#define LEID 30
#define NUID 40
enum NLOSTATUS
{
    NLOGOOD = 1,
    NOTIMPLEMENT = 0
};
int GetNLOFuncSFF(int S1,int F1,int F2,int type,CFunc_NLO &func);
int GetNLOFuncSFFC(int S1,int F1,int F2,int type,CFunc_NLO &func_PL, CFunc_NLO &func_PR);
int GetNLOFuncSVV(int S1,int V1,int V2,int type,CFunc_NLO &func_gmunu, CFunc_NLO &func_kmuqnu);
int GetNLOFuncSSV(int S1,int S2,int V1,int type,CFunc_NLO &func);
int GetNLOFuncSSS(int S1,int S2,int S3,int type,CFunc_NLO &func);

class THDM {

 public:

  /**
  * @brief Default constructor.
  *
  * Empty default constructor which does nothing except initialization of this
  * THDM object. The model is not specified in any way by using this constructor.
  */
  THDM();
  // ~THDM();

  /**
  * @brief Sets the underlying SM
  *
  * This method can be used to specify the SM parameters which this 2HDM
  * is using.
  *
  * @param sm_in SM object specifying the parameters to use in this 2HDM
  */
  void set_SM(SM sm_in);

  /**
  * @brief Returns the underlying SM
  *
  * @returns The SM object specifying the parameters used in this 2HDM
  */
  SM get_SM();

  /**
  * @brief Specifies 2HDM using generic potential
  *
  * This method lets the user specify the 2HDM using a generic basis.
  * For details on the basis choices available for the Higgs potential, we
  * refer to the complete manual.
  *
  * @param lambda1 Value of \f$ \lambda_1 \f$
  * @param lambda2 Value of \f$ \lambda_2 \f$
  * @param lambda3 Value of \f$ \lambda_3 \f$
  * @param lambda4 Value of \f$ \lambda_4 \f$
  * @param lambda5 Value of \f$ \lambda_5 \f$
  * @param lambda6 Value of \f$ \lambda_6 \f$
  * @param lambda7 Value of \f$ \lambda_7 \f$
  * @param m12_2 Soft \f$ Z_2 \f$-breaking parameter
  * @param tan_beta Ratio of vevs, \f$ \tan\beta=v_2/v_1 \f$
  *
  * @returns Boolean \a true if all parameters were set correctly, \a false otherwise
  */
  bool set_param_gen(double lambda1, double lambda2, double lambda3,
                     double lambda4, double lambda5, double lambda6,
                     double lambda7, double m12_2, double tan_beta);

  /**
  * @brief Specifies 2HDM in the Higgs basis
  *
  * This method lets the user specify the 2HDM in the Higgs basis \f$ (v_2\equiv 0) \f$.
  * For details on the basis choices available for the Higgs potential, we
  * refer to the complete manual.
  *
  * @param Lambda1 Value of \f$ \Lambda_1 \f$
  * @param Lambda2 Value of \f$ \Lambda_2 \f$
  * @param Lambda3 Value of \f$ \Lambda_3 \f$
  * @param Lambda4 Value of \f$ \Lambda_4 \f$
  * @param Lambda5 Value of \f$ \Lambda_5 \f$
  * @param Lambda6 Value of \f$ \Lambda_6 \f$
  * @param Lambda7 Value of \f$ \Lambda_7 \f$
  * @param m_Hp    Mass of the charged Higgs
  *
  * @returns Boolean \a true if all parameters were set correctly, \a false otherwise
  */
  bool set_param_higgs(double Lambda1, double Lambda2, double Lambda3,
                       double Lambda4, double Lambda5, double Lambda6,
                       double Lambda7, double m_Hp);

  /**
  * @brief Specifies 2HDM in the hybrid basis of 1507.04281
  *
  * @param mh Value of light CP-even Higgs mass \f$ m_h \f$
  * @param mH Value of heavy CP-even Higgs mass \f$ m_H \f$
  * @param cba Mixing parameter \f$ \cos(\beta-\alpha) \f$. Valid range: -1 < cba <= 1.
  * @param Z_4 Value of \f$ Z_4 \f$
  * @param Z_5 Value of \f$ Z_5 \f$
  * @param Z_7 Value of \f$ Z_7 \f$
  * @param tanb Ratio of vevs, \f$ \tan\beta=v_2/v_1 \f$
  *
  * @returns Boolean \a true if all parameters were set correctly, \a false otherwise
  */
  bool set_param_hybrid(double mh, double mH, double cba, double Z4,double Z5, double Z7, double tanb);

  /**
  * @brief Specifies 2HDM in the hybrid basis of 1507.04281, but with sba instead of cba
  *
  * @param mh Value of light CP-even Higgs mass \f$ m_h \f$
  * @param mH Value of heavy CP-even Higgs mass \f$ m_H \f$
  * @param sba Mixing parameter \f$ \sin(\beta-\alpha) \f$. Valid range: -1 < cba <= 1.
  * @param Z_4 Value of \f$ Z_4 \f$
  * @param Z_5 Value of \f$ Z_5 \f$
  * @param Z_7 Value of \f$ Z_7 \f$
  * @param tanb Ratio of vevs, \f$ \tan\beta=v_2/v_1 \f$
  *
  * @returns Boolean \a true if all parameters were set correctly, \a false otherwise
  */
  bool set_param_hybrid_sba(double mh, double mH, double sba, double Z4,double Z5, double Z7, double tanb);

  /**
  * @brief Specifies 2HDM using potential from the Higgs Hunter's Guide
  *
  * This method lets the user specify the 2HDM using the CP-conserving form
  * of the potential given by Eq. (4.8) in "The Higgs Hunter's Guide".
  * For details on the basis choices available for the Higgs potential, we
  * refer to the complete manual.
  *
  * @param lambda1 Value of \f$ \lambda_1 \f$
  * @param lambda2 Value of \f$ \lambda_2 \f$
  * @param lambda3 Value of \f$ \lambda_3 \f$
  * @param lambda4 Value of \f$ \lambda_4 \f$
  * @param lambda5 Value of \f$ \lambda_5 \f$
  * @param lambda6 Value of \f$ \lambda_6 \f$
  * @param tan_beta Ratio of vevs, \f$ \tan\beta=v_2/v_1 \f$
  *
  * @returns Boolean \a true if all parameters were set correctly, \a false otherwise
  */
  bool set_param_HHG(double lambda1, double lambda2, double lambda3,
                     double lambda4, double lambda5, double lambda6,
                     double tan_beta);

  /**
  * @brief Specifies 2HDM in the physical basis
  *
  * This method lets the user specify the 2HDM using the basis of physical
  * Higgs masses. For details on the basis choices available for the Higgs
  * potential, we refer to the complete manual.
  *
  * @param m_h  Mass of lightest CP-even Higgs \f$ h \f$
  * @param m_H  Mass of heavier CP-even Higgs \f$ H \f$
  * @param m_A  Mass of CP-odd Higgs \f$ A \f$
  * @param m_Hp Mass of charged Higgs
  * @param sba  Mixing parameter \f$ \sin(\beta-\alpha) \f$. NB: Correct sign on \f$ \sin(\beta-\alpha) \f$ must be determined from the condition \f$ \cos(\beta-\alpha) \geq 0 \f$
  * @param lambda6  Value of \f$ \lambda_6 \f$ in generic potential
  * @param lambda7  Value of \f$ \lambda_7 \f$ in generic potential
  * @param m12_2    Soft \f$ Z_2 \f$-breaking parameter
  * @param tan_beta Ratio of vevs, \f$ \tan\beta=v_2/v_1 \f$
  *
  * @returns Boolean \a true if all parameters were set correctly, \a false otherwise
  */
  bool set_param_phys(double m_h,double m_H, double m_A, double m_Hp,
                      double sba, double lambda6, double lambda7,
                      double m12_2, double tan_beta);

  bool set_param_sm(double mh);

  /**
  * @brief Specifies the 2HDM of the tree-level MSSM
  *
  * This method lets the user specify a 2HDM with the properties of the tree-level
  * MSSM in terms of masses and coupling relations. The Yukawa sector is also
  * automatically selected to be of type II.
  *
  * @param m_A      Mass of CP-odd Higgs \f$ A \f$
  * @param tan_beta Ratio of vevs, \f$ \tan\beta=v_2/v_1 \f$
  *
  * @returns Boolean \a true if all parameters were set correctly, \a false otherwise
  */
  bool set_MSSM(double m_A, double tan_beta);

  /**
  * @brief Specifies the 2HDM of the tree-level MSSM + mass corrections to h ("hMSSM")
  *
  * This method lets the user specify a 2HDM with the properties of the tree-level
  * MSSM, with additional mass corrections from the lambda_2 contribution to the (2,2)
  * element of the mass matrix. These can be identified as the leading (mt^4) MSSM
  * corrections. The Yukawa sector is automatically selected to be of type II.
  *
  * @param m_h      Mass of light CP-even Higgs \f$ h \f$
  * @param m_A      Mass of CP-odd Higgs \f$ A \f$
  * @param tan_beta Ratio of vevs, \f$ \tan\beta=v_2/v_1 \f$
  *
  * @returns Boolean \a true if all parameters were set correctly, \a false otherwise
  */
  bool set_hMSSM(double mh, double mA, double tanb);

  /**
  * @brief Specifies the 2HDM with one "inert Higgs" doublet
  *
  * This method lets the user specify a 2HDM with special properties as follows:
  * only one of the doublets acquires a vev, there is an exact \f$ Z_2 \f$-symmetry,
  * preventing interdoublet mixing, and the doublet without a vev has no Yukawa
  * couplings. This results in one doublet being "inert" (or dark). The lightest
  * Higgs originating from this doublet is then stable, thus a dark matter candidate.
  * The conventions used for specifying the parameters for this model are the same as
  * in arXiv:0810.3924.
  *
  * @param m_h  Mass of "SM-like" Higgs - NB: Changed meaning compared to "physical" basis
  * @param m_H  Mass of CP-even "inert" Higgs - NB: Changed meaning compared to "physical" basis
  * @param m_A  Mass of CP-odd "inert" Higgs
  * @param m_Hp Mass of charged "inert" Higgs
  * @param lambda2 Value of \f$ \lambda_2 \f$ parameter
  * @param lambda3 Value of \f$ \lambda_2 \f$ parameter
  *
  * @returns Boolean \a true if all parameters were set correctly, \a false otherwise
  */
  bool set_inert(double m_h,double m_H, double m_A, double m_Hp, double lambda2, double lambda3);

  /**
  * @brief Returns parameter set in the generic basis
  *
  * This method returns a consistent set of parameter values describing the
  * current model in the generic basis.
  *
  * @param lambda1 Returned value of \f$ \lambda_1 \f$
  * @param lambda2 Returned value of \f$ \lambda_2 \f$
  * @param lambda3 Returned value of \f$ \lambda_3 \f$
  * @param lambda4 Returned value of \f$ \lambda_4 \f$
  * @param lambda5 Returned value of \f$ \lambda_5 \f$
  * @param lambda6 Returned value of \f$ \lambda_6 \f$
  * @param lambda7 Returned value of \f$ \lambda_7 \f$
  * @param m12_2   Returned value of \f$ m_{12}^2 \f$
  * @param tan_beta Returned value of \f$ \tan\beta \f$
  */
  void get_param_gen(double &lambda1, double &lambda2, double &lambda3,
                     double &lambda4, double &lambda5, double &lambda6,
                     double &lambda7, double &m12_2,   double &tan_beta);

  /**
  * @brief Returns parameter set in the Higgs basis
  *
  * This method returns a consistent set of parameter values describing the
  * current model in the Higgs basis.
  *
  * @param Lambda1 Returned value of \f$ \Lambda_1 \f$
  * @param Lambda2 Returned value of \f$ \Lambda_2 \f$
  * @param Lambda3 Returned value of \f$ \Lambda_3 \f$
  * @param Lambda4 Returned value of \f$ \Lambda_4 \f$
  * @param Lambda5 Returned value of \f$ \Lambda_5 \f$
  * @param Lambda6 Returned value of \f$ \Lambda_6 \f$
  * @param Lambda7 Returned value of \f$ \Lambda_7 \f$
  * @param m_Hp    Returned value of the charged Higgs mass
  */
  void get_param_higgs(double &Lambda1, double &Lambda2, double &Lambda3,
                       double &Lambda4, double &Lambda5, double &Lambda6,
                       double &Lambda7, double &m_Hp);

  void get_param_hybrid(double &m_h, double &m_H, double &sba,
                        double &Z4, double &Z5, double &Z7, double &tan_beta);

  /**
  * @brief Returns parameter set in Higgs Hunter's Guide basis
  *
  * This method returns a consistent set of parameter values describing the
  * current model in the basis used in the Higgs Hunter's Guide. NB: The use
  * of this method assumes \f$ \lambda_6=\lambda_7=0 \f$ in the generic basis.
  *
  * @param lambda1 Returned value of \f$ \lambda_1 \f$
  * @param lambda2 Returned value of \f$ \lambda_2 \f$
  * @param lambda3 Returned value of \f$ \lambda_3 \f$
  * @param lambda4 Returned value of \f$ \lambda_4 \f$
  * @param lambda5 Returned value of \f$ \lambda_5 \f$
  * @param lambda6 Returned value of \f$ \lambda_6 \f$
  * @param tan_beta Returned value of \f$ \tan\beta \f$
  */
  void get_param_HHG(double &lambda1, double &lambda2, double &lambda3,
                     double &lambda4, double &lambda5, double &lambda6,
                     double &tan_beta);

  /**
  * @brief Returns parameter set in physical basis
  *
  * This method returns a consistent set of parameter values describing the
  * current model in the "physical" basis.
  *
  * @param m_h      Returned value of lightest CP-even Higgs mass
  * @param m_H      Returned value of heaviest CP-even Higgs mass
  * @param m_A      Returned value of CP-odd Higgs mass
  * @param m_Hp     Returned value of charged Higgs mass
  * @param sba      Returned value of \f$ \sin(\beta-\alpha) \f$
  * @param lambda6  Returned value of \f$ \lambda_6 \f$
  * @param lambda7  Returned value of \f$ \lambda_7 \f$
  * @param m12_2    Returned value of \f$ m_{12}^2 \f$
  * @param tan_beta Returned value of \f$ \tan\beta \f$
  */
  void get_param_phys(double &m_h,double &m_H, double &m_A, double &m_Hp,
                      double &sba, double &lambda6, double &lambda7,
                      double &m12_2, double &tan_beta);

  /**
  * @brief Changes basis for the Higgs doublets
  *
  * This method performs a change of basis, changing the value of
  * \f$ \tan\beta \f$. This results in a recalculation of all the other
  * potential parameters, but the physical couplings remain unchanged.
  * Yukawa couplings are not modified.
  *
  * @param tan_beta Value of \f$ \tan\beta \f$ to use in the new basis
  */
  void recalc_tan_beta(double tan_beta);

  /**
  * @brief Returns the mass of physical Higgs bosons
  *
  * This method returns the mass of a physical Higgs Boson. The numbering
  * convention corresponds to (1,2,3,4 = h,H,A,H+) where always \f$ m_h < m_H \f$.
  *
  * @param h Index of Higgs boson
  *
  * @returns The mass of Higgs boson \a h
  */
  double get_hmass(int h);

  /**
  * @brief Returns invariant \f$ \sin(\beta-\alpha) \f$
  *
  * @returns Value of \f$ \sin(\beta-\alpha) \f$ in model
  */
  double get_sba();

  /**
  * @brief Returns invariant \f$ \cos(\beta-\alpha) \f$
  *
  * @returns Value of \f$ \cos(\beta-\alpha) \f$ in model
  */
  double get_cba();

  /**
  * @brief Higgs coupling \f$ q_{ki} \f$ factors
  *
  * This method returns the invariants \f$ q_{ki} \f$ (hep-ph/0602242) which are used
  * for the triple and quartic Higgs couplings.
  *
  * @param k Higgs index (1--4)
  * @param i Coupling index (1--2)
  *
  * @returns The value of the coefficient \f$ q_{ki} \f$
  */
  complex <double> get_qki(int k, int i);

  /**
  * @brief Initializes complete Yukawa sector to a specific type
  *
  * This method is used to specify a type of Yukawa sector for the 2HDM.
  * The types (1-4) implemented follow the convention of .
  *
  * @param type Type of Yukawa sector (1--4)
  */
  void set_yukawas_type(int type);

  /**
  * @brief Initializes and sets diagonal Yukawa couplings for down-type quarks
  *
  * This method initializes and sets the Yukawa couplings for the down-type quarks.
  * The diagonal matrix \f$ \kappa^D \f$ is automatically specified to comply with
  * the quark pole masses, whereas the diagonal elements of \f$ \rho^D \f$ can be
  * specified as input.
  *
  * @param rhod Diagonal element of \f$ \rho^D \f$ for the \f$ d \f$ quark
  * @param rhos Diagonal element of \f$ \rho^D \f$ for the \f$ s \f$ quark
  * @param rhob Diagonal element of \f$ \rho^D \f$ for the \f$ b \f$ quark
  *
  * @see set_yukawas_up, set_yukawas_lepton
  */
  void set_yukawas_down(double rhod, double rhos, double rhob);

  /**
  * @brief Initializes and sets diagonal Yukawa couplings for up-type quarks
  *
  * This method initializes and sets the Yukawa couplings for the up-type quarks.
  * The diagonal matrix \f$ \kappa^U \f$ is automatically specified to comply with
  * the quark pole masses, whereas the diagonal elements of \f$ \rho^U \f$ can be
  * specified as input.
  *
  * @param rhou Diagonal element of \f$ \rho^U \f$ for the \f$ u \f$ quark
  * @param rhoc Diagonal element of \f$ \rho^U \f$ for the \f$ c \f$ quark
  * @param rhot Diagonal element of \f$ \rho^U \f$ for the \f$ t \f$ quark
  *
  * @see set_yukawas_down, set_yukawas_lepton
  */
  void set_yukawas_up(double rhou, double rhoc, double rhot);

  /**
  * @brief Initializes and sets diagonal Yukawa couplings for the charged leptons
  *
  * This method initializes and sets the Yukawa couplings for the leptons.
  * The diagonal matrix \f$ \kappa^L \f$ is automatically specified to comply with
  * the lepton pole masses, whereas the diagonal element of \f$ \rho^L \f$ can be
  * specified as input.
  *
  * @param rhoe   Diagonal element of \f$ \rho^L \f$ for \f$ e \f$
  * @param rhomu  Diagonal element of \f$ \rho^L \f$ for \f$ \mu \f$
  * @param rhotau Diagonal element of \f$ \rho^L \f$ for \f$ \tau \f$
  *
  * @see set_yukawas_down, set_yukawas_up
  */
  void set_yukawas_lepton(double rhoe, double rhomu, double rhotau);

  /**
  * @brief Initializes and sets Yukawa couplings for down-type quarks
  *
  * This method initializes and sets the Yukawa couplings for the down-type quarks.
  * The diagonal matrix \f$ \kappa^D \f$ is automatically specified to comply with
  * the quark pole masses, whereas the symmetric matrix \f$ \rho^D \f$ is given by
  * the six elements specified as input.
  *
  * @param rho11 Yukawa coupling \f$ \rho^D_{dd} \f$
  * @param rho22 Yukawa coupling \f$ \rho^D_{ss} \f$
  * @param rho33 Yukawa coupling \f$ \rho^D_{bb} \f$
  * @param rho12 Yukawa coupling \f$ \rho^D_{ds} \f$
  * @param rho13 Yukawa coupling \f$ \rho^D_{db} \f$
  * @param rho23 Yukawa coupling \f$ \rho^D_{sb} \f$
  */
  void set_yukawas_down(double rho11,double rho22,double rho33,double rho12,double rho13,double rho23);

  /**
  * @brief Initializes and sets Yukawa couplings for up-type quarks
  *
  * This method initializes and sets the Yukawa couplings for the up-type quarks.
  * The diagonal matrix \f$ \kappa^U \f$ is automatically specified to comply with
  * the quark pole masses, whereas the symmetric matrix \f$ \rho^U \f$ is given by
  * the six elements specified as input.
  *
  * @param rho11 Yukawa coupling \f$ \rho^U_{uu} \f$
  * @param rho22 Yukawa coupling \f$ \rho^U_{cc} \f$
  * @param rho33 Yukawa coupling \f$ \rho^U_{tt} \f$
  * @param rho12 Yukawa coupling \f$ \rho^U_{uc} \f$
  * @param rho13 Yukawa coupling \f$ \rho^U_{ut} \f$
  * @param rho23 Yukawa coupling \f$ \rho^U_{ct} \f$
  */
  void set_yukawas_up(double rho11,double rho22,double rho33,double rho12,double rho13,double rho23);

  /**
  * @brief Initializes and sets Yukawa couplings for leptons
  *
  * This method initializes and sets the Yukawa couplings for the leptons.
  * The diagonal matrix \f$ \kappa^L \f$ is automatically specified to comply with
  * the lepton masses, whereas the symmetric matrix \f$ \rho^L \f$ is given by
  * the six elements specified as input.
  *
  * @param rho11 Yukawa coupling \f$ \rho^L_{ee} \f$
  * @param rho22 Yukawa coupling \f$ \rho^L_{\mu\mu} \f$
  * @param rho33 Yukawa coupling \f$ \rho^L_{\tau\tau} \f$
  * @param rho12 Yukawa coupling \f$ \rho^L_{e\mu} \f$
  * @param rho13 Yukawa coupling \f$ \rho^L_{e\tau} \f$
  * @param rho23 Yukawa coupling \f$ \rho^L_{\mu\tau} \f$
  */
  void set_yukawas_lepton(double rho11,double rho22,double rho33,double rho12,double rho13,double rho23);

  /**
  * @brief Initializes and sets Yukawa couplings for the inert 2HDM
  *
  * This method initializes and sets the Yukawa couplings for the "inert"
  * model. That means there are no Yukawa couplings of any type, except what
  * is necessary for the mass terms, i.e. all \f$ \rho = 0 \f$.
  */
  void set_yukawas_inert();

  /**
  * @brief Returns diagonal elements of Yukawa matrix for down-type quarks
  *
  * @param rhod Returned value of \f$ \rho^D_{dd} \f$
  * @param rhos Returned value of \f$ \rho^D_{ss} \f$
  * @param rhob Returned value of \f$ \rho^D_{bb} \f$
  */
  void get_yukawas_down(double &rhod, double &rhos, double &rhob);

  /**
  * @brief Returns diagonal elements of Yukawa matrix for up-type quarks
  *
  * @param rhou Returned value of \f$ \rho^U_{uu} \f$
  * @param rhoc Returned value of \f$ \rho^U_{cc} \f$
  * @param rhot Returned value of \f$ \rho^U_{tt} \f$
  */
  void get_yukawas_up(double &rhou, double &rhoc, double &rhot);

  /**
  * @brief Returns diagonal elements of Yukawa matrix for leptons
  *
  * @param rhoe Returned value of \f$ \rho^L_{ee} \f$
  * @param rhomu Returned value of \f$ \rho^L_{\mu\mu} \f$
  * @param rhotau Returned value of \f$ \rho^L_{\tau\tau} \f$
  */
  void get_yukawas_lepton(double &rhoe, double &rhomu, double &rhotau);

  void get_kappa_down(double &kd, double &ks, double &kb);
  void get_kappa_up(double &ku, double &kc, double &kt);
  void get_kappa_lepton(double &ke, double &kmu, double &ktau);

  void get_kappa_down(double mu, double &kd, double &ks, double &kb);
  void get_kappa_up(double mu, double &ku, double &kc, double &kt);
  void get_kappa_lepton(double mu, double &ke, double &kmu, double &ktau);
  void get_rho_down(double mu, double &rd, double &rs, double &rb);
  void get_rho_up(double mu, double &ru, double &rc, double &rt);
  void get_rho_lepton(double mu, double &re, double &rmu, double &rtau);

  /**
  * @brief Returns Yukawa matrix for down-type quarks
  *
  * @param rho_D_out Returned Yukawa matrix \f$ \rho^D \f$
  */
  void get_yukawas_down(gsl_matrix *rho_D_out);

  /**
  * @brief Returns Yukawa matrix for up-type quarks
  *
  * @param rho_U_out Returned Yukawa matrix \f$ \rho^U \f$
  */
  void get_yukawas_up(gsl_matrix *rho_U_out);

  /**
  * @brief Returns Yukawa matrix for leptons
  *
  * @param rho_L_out Returned Yukawa matrix \f$ \rho^L \f$
  */
  void get_yukawas_lepton(gsl_matrix *rho_L_out);

  /**
  * @brief Couplings of Higgses to down-type fermions
  *
  * Calculates the coupling \f$ hdd \f$ between one physical Higgs state, specified
  * by \a h, and two down-type quarks \a f1 and \a f2.
  *
  * @param h  Index of Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param f1 First fermion (1,2,3 = d,s,b)
  * @param f2 Second fermion (1,2,3 = d,s,b)
  * @param cs Returned (complex) value for scalar coupling
  * @param cp Returned (complex) value for pseudoscalar coupling
  */
  void get_coupling_hdd(int h,int f1,int f2,complex <double> &cs, complex <double> &cp);
  void get_coupling_hdd_NLO(int h,int f1,int f2,complex <double> &cs, complex <double> &cp);

  /**
  * @brief Couplings of Higgses to up-type fermions
  *
  * Calculates the coupling \f$ huu \f$ between one physical Higgs state, specified
  * by \a h, and two up-type quarks \a f1 and \a f2.
  *
  * @param h  Index of Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param f1 First fermion (1,2,3 = u,c,t)
  * @param f2 Second fermion (1,2,3 = u,c,t)
  * @param cs Returned (complex) value for scalar coupling
  * @param cp Returned (complex) value for pseudoscalar coupling
  */
  void get_coupling_huu(int h,int f1,int f2,complex <double> &cs, complex <double> &cp);
  void get_coupling_huu_NLO(int h,int f1,int f2,complex <double> &cs, complex <double> &cp);

  /**
  * @brief Couplings of Higgses to mixed type fermions
  *
  * Calculates the coupling \f$ hdu \f$ between one physical Higgs state, specified
  * by \a h (only relevant one is charged Higgs), and two quarks \a f1 and \a f2.
  *
  * @param h  Index of Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param d Down-type fermion (1,2,3 = d,s,b)
  * @param u Up-type fermion (1,2,3 = u,c,t)
  * @param cs Returned (complex) value for scalar coupling
  * @param cp Returned (complex) value for pseudoscalar coupling
  */
  void get_coupling_hdu(int h,int d,int u,complex <double> &cs, complex <double> &cp);
  void get_coupling_hdu_NLO(int h,int d,int u,complex <double> &cs, complex <double> &cp);

  /**
  * @brief Couplings of Higgses to charged leptons
  *
  * Calculates the coupling \f$ hll \f$ between one physical Higgs state, specified
  * by \a h, and two leptons \a f1 and \a f2.
  *
  * @param h  Index of Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param f1 First fermion (1,2,3 = \f$ e,\mu,\tau \f$)
  * @param f2 Second fermion (1,2,3 = \f$ e,\mu,\tau \f$)
  * @param cs Returned (complex) value for scalar coupling
  * @param cp Returned (complex) value for pseudoscalar coupling
  */
  void get_coupling_hll(int h,int f1,int f2,complex <double> &cs, complex <double> &cp);
  void get_coupling_hll_NLO(int h,int f1,int f2,complex <double> &cs, complex <double> &cp);
  /**
  * @brief Couplings of Higgses to mixed leptons
  *
  * Calculates the coupling \f$ hl\nu_l \f$ between one physical Higgs state, specified
  * by \a h (only relevant one is charged Higgs), and two leptons \a f1 and \a f2.
  *
  * @param h  Index of Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param l  Charged lepton (1,2,3 = \f$ e,\mu,\tau \f$)
  * @param n  Neutrino (1,2,3 = \f$ \nu_e,\nu_\mu,\nu_\tau \f$)
  * @param cs Returned (complex) value for scalar coupling
  * @param cp Returned (complex) value for pseudoscalar coupling
  */
  void get_coupling_hln(int h,int l,int n,complex <double> &cs, complex <double> &cp);
  void get_coupling_hln_NLO(int h,int l,int n,complex <double> &cs, complex <double> &cp);

  /**
  * @brief Couplings of Higgses to pairs of vector bosons
  *
  * Calculates the coupling \f$ hV_1 V_2 \f$ between one physical Higgs state, specified
  * by \a h , and two vector bosons \a v1 and \a v2. NB: Neutral Higgses have no coupling
  * to photons implemented, but the loop mediated decays \f$ h\to \gamma \gamma \f$ can
  * nevertheless be calculated using the DecayTable class. Conventions are according to hep-ph/0602242.
  *
  * @param v1 Index of first vector boson (1,2,3 = \f$ \gamma,Z,W^+ \f$)
  * @param v2 Index of second vector boson (1,2,3 = \f$ \gamma,Z,W^+ \f$)
  * @param h  Index of Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param c  Returned (complex) value for coupling
  */
  void get_coupling_vvh(int v1,int v2,int h,complex <double> &c);
  void get_coupling_vvh_NLO(int v1,int v2,int h,complex <double> &c_gmunu,complex <double> &c_kmuqnu);

  /**
  * @brief Couplings of vector bosons to pairs of Higgses
  *
  * Calculates the coupling \f$ Vh_1 h_2 \f$ between two physical Higgs states, specified
  * by \a h1 and \a h2, and one vector boson \a v. Conventions are according to hep-ph/0602242.
  *
  * @param h1 Index of first Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param h2 Index of second Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param v  Index of vector boson (1,2,3 = \f$ \gamma,Z,W^+ \f$)
  * @param c  Returned (complex) value for coupling
  */
  void get_coupling_vhh(int v,int h1,int h2,complex <double> &c);
  void get_coupling_vhh_NLO(int v,int h1,int h2,complex <double> &c);

  /**
  * @brief Triple Higgs couplings
  *
  * Calculates the coupling \f$ h_1 h_2 h_3 \f$ between three physical Higgs states.
  * Conventions are according to hep-ph/0602242
  *
  * @param h1 Index of first Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param h2 Index of second Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param h3 Index of third Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param c  Returned (complex) value for coupling
  */
  void get_coupling_hhh(int h1,int h2,int h3,complex <double> &c);
  void get_coupling_hhh_NLO(int h1,int h2,int h3,complex <double> &c);

  /**
  * @brief Couplings of two vector bosons and two Higgses
  *
  * Calculates the coupling \f$ V_1 V_2 h_1 h_2 \f$ between two vector bosons and
  * two physical Higgs states. Conventions are according to hep-ph/0602242
  *
  * @param v1 Index of first vector boson (1,2,3 = \f$ \gamma,Z,W^+ \f$)
  * @param v2 Index of second vector boson (1,2,3 = \f$ \gamma,Z,W^+ \f$)
  * @param h1 Index of first Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param h2 Index of second Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param c  Returned (complex) value for coupling
  */
  void get_coupling_vvhh(int v1,int v2,int h1,int h2,complex <double> &c);

  /**
  * @brief Quartic Higgs couplings
  *
  * Calculates the coupling \f$ h_1 h_2 h_3 h_4 \f$ between four physical Higgs states.
  * Conventions are according to hep-ph/0602242
  *
  * @param h1 Index of first Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param h2 Index of second Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param h3 Index of third Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param h4 Index of fourth Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param c  Returned (complex) value for coupling
  */
  void get_coupling_hhhh(int h1,int h2,int h3,int h4,complex <double> &c);

  /**
  * @brief Calculates tree-level unitarity constraints
  *
  * This method calculates the eigenvalues of the S-matrix for Higgs-Higgs scattering
  * as defined in hep-ph/0508020.
  *
  * @returns Value of largest eigenvalue
  */
  double calc_unitarity();

  /**
  * @brief Checks tree-level unitarity constraints
  *
  * This method checks whether the parameters of the Higgs potential results
  * in an S-matrix for Higgs-Higgs scattering that fullfills tree-level unitarity,
  * as discussed in hep-ph/0508020.
  *
  * @param unitarity_limit optional value to be used for unitarity limit (default = \f$ 16\pi \f$)
  *
  * @returns Boolean \a true if the unitarity constraint is satisfied, \a false otherwise
  */
  bool check_unitarity(double unitarity_limit = 16*M_PI);

  /**
  * @brief Calculates largest quartic Higgs coupling
  *
  * This method calculates all the quartic Higgs boson couplings and returns the largest one as well as the indices of the four Higgs bosons
  *
  * @param gmax  Returned (complex) value for coupling
  * @param imax  Returned index of first Higgs boson (1,2,3,4 = h,H,A,H+)
  * @param jmax  Returned index of second boson (1,2,3,4 = h,H,A,H+)
  * @param kmax  Returned index of third boson (1,2,3,4 = h,H,A,H+)
  * @param lmax  Returned index of fourth boson (1,2,3,4 = h,H,A,H+)
  */
  void calc_perturbativity(complex <double> &gmax,int &imax,int &jmax,int &kmax,int &lmax);

  /**
  * @brief Checks perturbativity
  *
  * This method checks whether the couplings of the Higgs and Gauge bosons fullfills perturbativity
  *
  * @param perturbativity_limit optional value to be used for perturbativity limit (default = \f$ 4\pi \f$)
  *
  * @returns Boolean \a true if the perturbativity constraint is satisfied, \a false otherwise
  */
  bool check_perturbativity(double perturbativity_limit = 4*M_PI);

  /**
  * @brief Checks the stability of the Higgs potential
  *
  * This method checks whether the Higgs potential is stable, i.e. if no
  * directions exist in field space for which \f$ V\to -\infty \f$ for large
  * values of the fields. This is done through a combination of analytical and numerical
  * algorithms, depending on the generality of the specified potential.
  *
  * @returns Boolean \a true if the potential is deemed stable, \a false otherwise
  */
  bool check_stability();

  /**
  * @brief Prints the potential parameters in the generic basis to stdout
  */
  void print_param_gen();

  /**
  * @brief Prints the potential parameters in the Higgs basis to stdout
  */
  void print_param_higgs();

  /**
  * @brief Prints the potential parameters in the hybrid basis to stdout
  */
  void print_param_hybrid();

  /**
  * @brief Prints the potential parameters in the physical basis to stdout
  */
  void print_param_phys();

  /**
  * @brief Prints the potential parameters in the Higgs Hunter's Guide basis to stdout
  */
  void print_param_HHG();

  /**
  * @brief Prints the Yukawa matrices to stdout
  */
  void print_yukawas();

  void print_hdecay();

  /**
  * @brief Reads the 2HDM parameters from a LesHouches compliant file
  *
  * This method reads the parameters necessary to specify the 2HDM (and the %SM)
  * from a file complying with the LesHouches standard. There are several options
  * for how to prepare this file (see manual for details,
  * or the code distribution for examples).
  *
  * @param file The name of the file to read
  *
  * @returns Boolean \a true if the file could be opened and read correctly AND
  *          a 2HDM was correctly specified in the file, \a false otherwise
  */
  bool read_LesHouches(const char* file);

  /**
  * @brief Writes output in a LesHouches compliant file format
  *
  * This method writes the potential parameters and other selected output to
  * a specified file in LesHouches-compliant file format which can then be
  * further processed by other codes.
  *
  * @param file The name of the file which is to be written
  * @param fulldecay If \a true, decay modes of the Higgs bosons in LesHouches
  *                  format are written to the file
  * @param couplings If \a true, all couplings (> 200 values) for the Higgs bosons
  *                  are written to the file. Should be used when supplying input
  *                  for MadGraph/MadEvent 2HDMC model
  * @param qcd_on    Turns QCD corrections on or off, default is on. Should be
  *                  turned off when supplying input for MadGraph/MadEvent 2HDMC model
  */
  void write_LesHouches(const char* file, bool fulldecay, bool couplings, bool qcd_on=true, bool HBHS=true);

  void write_model(const char* file);

  double get_alpha();

  /**
	* @brief Small value
	*
	*  Minimum value used for widths, branching ratios etc. to determine when
	*  something should be considered zero.
	*/
  const static double EPS = 1E-15;

 private:
  double      lambda[8];
  double      beta;
  double      m22_2;
  double      sinba;
  bool        params_set;
  double      v2;
  gsl_matrix *kappa_D;
  gsl_matrix *kappa_U;
  gsl_matrix *kappa_L;
  gsl_matrix *rho_D;
  gsl_matrix *rho_U;
  gsl_matrix *rho_L;
  gsl_matrix *rho_N;
  int         yukawas_type;

  static bool first_run;
  const static char *version;

  SM sm;

  void init();
  double get_m12_2();
  void set_kappa();
  void set_kappa_D();
  void set_kappa_U();
  void set_kappa_L();

  void print_info();

// Added by Y.Wu
  bool Updated;
  bool HpmUpdated;
  double L_MHL2, L_MHH2, L_MHA2, L_MHP2;
  double L_MHL, L_MHH, L_MHA, L_MHP, L_sba, L_L6, L_L7, L_M122, L_tb;
  double L_beta, L_alp;
  double L_M2;
  gsl_matrix *rho_D_NLO;
  gsl_matrix *rho_U_NLO;
  gsl_matrix *RD_NLO;// = gsl_matrix_alloc(3,3);
  gsl_matrix *RU_NLO;// = gsl_matrix_alloc(3,3);

  // To save time, we can first check `Updated` to see if we need to rerun this function
  void store_param_phys();
  // void store_Hpmff_Couplings();

};

#endif


/* specfunc/legendre.h
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Gerard Jungman
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* Author:  G. Jungman */

/* Declare private but non-local support functions
 * used in various Legendre function evaluations.
 */

#include <gsl/gsl_sf_result.h>

/* Large negative mu asymptotic
 * P^{-mu}_{-1/2 + I tau}, mu -> Inf
 * |x| < 1
 */
int
gsl_sf_conicalP_xlt1_large_neg_mu_e(double mu, double tau, double x,
                                       gsl_sf_result * result, double * ln_multiplier);

/* Large tau uniform asymptotics
 * P^{-mu}_{-1/2 + I tau}, tau -> Inf
 * 1 < x
 */
int
gsl_sf_conicalP_xgt1_neg_mu_largetau_e(const double mu, const double tau,
                                          const double x, double acosh_x,
                                          gsl_sf_result * result, double * ln_multiplier);

/* Large tau uniform asymptotics
 * P^{-mu}_{-1/2 + I tau}, tau -> Inf
 * -1 < x < 1
 */
int
gsl_sf_conicalP_xlt1_neg_mu_largetau_e(const double mu, const double tau,
                                          const double x, const double acos_x,
                                          gsl_sf_result * result, double * ln_multiplier);

/* P^{mu}_{-1/2 + I tau}
 * x->Inf
 *
 *  * This is effective to precision EPS for
 *
 *    (mu^2 + tau^2)/((1 + tau^2)^(1/2) x^2) < EPS^{1/3}
 *
 * since it goes only to a fixed order, based on the
 * representation in terms of hypegeometric functions
 * of argument 1/x^2.
 * [Zhurina+Karmazina, (3.8)]
 */
int
gsl_sf_conicalP_large_x_e(const double mu, const double tau, const double x,
                             gsl_sf_result * result, double * ln_multiplier);


/**
 * \author Sylvain Marsat, University of Maryland - NASA GSFC
 *
 * \brief C code for example generation of waveforms with the EOBNRv2HM reduced order model,
 * processing through the Fourier-domain LISA response and SNR calculation.
 *
 */

#define _XOPEN_SOURCE 500

#ifdef __GNUC__
#define UNUSED __attribute__ ((unused))
#else
#define UNUSED
#endif

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <string.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_bspline.h>
#include <gsl/gsl_blas.h>
#include <gsl/gsl_min.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_complex.h>

#include "constants.h"
#include "struct.h"
#include "EOBNRv2HMROMstruct.h"
#include "EOBNRv2HMROM.h"
#include "wip.h"
#include "LISAgeometry.h"
#include "LISAFDresponse.h"

/* Parameters for the generation of a ROM waveform (in the form of a list of modes) */
/* All parameters are to be given in SI units! */
typedef struct tagROMParams {
  int nbmode;                /* Number of modes to generate (starting with the 22) - defaults to 1 (22 mode only) */
  double tRef;               /* shift in time with respect to the 22-fit-removed waveform */
  double phiRef;             /* phase at fRef */
  double fRef;               /* reference frequency */
  double m1;                 /* mass of companion 1 */
  double m2;                 /* mass of companion 2 */
  double distance;           /* distance of source */
  char outname[256];         /* file to which output should be written */
} ROMParams;

/* Parameters for the generation of a ROM waveform (in the form of a list of modes) */
/* All parameters are in SI units in the internals */
/* Angle definitions are taken from the Krolak&al paper gr-qc/0401108 */
typedef struct tagLISAParams {
  int nbmode;                /* Number of modes to generate (starting with the 22) - defaults to 1 (22 mode only) */
  double tRef;               /* shift in time with respect to the 22-fit-removed waveform */
  double phiRef;             /* phase at fRef */
  double fRef;               /* reference frequency */
  double m1;                 /* mass of companion 1 */
  double m2;                 /* mass of companion 2 */
  double distance;           /* distance of source */
  double inclination;        /* inclination of L relative to line of sight */
  double lambda;             /* First angle for the position in the sky of the source */
  double beta;               /* Second angle for the position in the sky of the source */
  double psi;                /* Polarization angle */
  char outname[256];         /* file to which output should be written */
} LISAParams;

/* Parse command line and return a newly allocated ROMParams object
 * Masses are input in solar masses and distances in Mpc - converted in SI for the internals */
static ROMParams* parse_args_ROM(ssize_t argc, char **argv) {
    ssize_t i;
    ROMParams* params;
    params = (ROMParams*) malloc(sizeof(ROMParams));
    memset(params, 0, sizeof(ROMParams));

    /* Set default values to the arguments */
    params->nbmode = 1;
    params->tRef = 0.;
    params->phiRef = 0.;
    params->fRef = 0.;
    params->m1 = 1. * 1e6 * MSUN_SI;
    params->m2 = 1. * 1e6 * MSUN_SI;
    params->distance = 1. * 1e9 * PC_SI;

    /* consume command line */
    for (i = 1; i < argc; ++i) {
        if (strcmp(argv[i], "--nbmode") == 0) {
            params->nbmode = atof(argv[++i]);
        } else if (strcmp(argv[i], "--tRef") == 0) {
            params->tRef = atof(argv[++i]);
        } else if (strcmp(argv[i], "--phiRef") == 0) {
            params->phiRef = atof(argv[++i]);
        } else if (strcmp(argv[i], "--fRef") == 0) {
            params->fRef = atof(argv[++i]);
        } else if (strcmp(argv[i], "--m1") == 0) {
            params->m1 = atof(argv[++i]) * MSUN_SI;
        } else if (strcmp(argv[i], "--m2") == 0) {
            params->m2 = atof(argv[++i]) * MSUN_SI;
        } else if (strcmp(argv[i], "--distance") == 0) {
            params->distance = atof(argv[++i]) * 1e6 * PC_SI;
        } else if (strcmp(argv[i], "--outname") == 0) {
            strncpy(params->outname, argv[++i], 256);
        } else {
            printf("Error: invalid option: %s\n", argv[i]);
            goto fail;
        }
    }

    return params;

    fail:
    free(params);
    exit(1);
}

/* Parse command line and return a newly allocated LISAParams object
 * Masses are input in solar masses and distances in Mpc - converted in SI for the internals */
static LISAParams* parse_args_LISA(ssize_t argc, char **argv) {
    ssize_t i;
    LISAParams* params;
    params = (LISAParams*) malloc(sizeof(LISAParams));
    memset(params, 0, sizeof(LISAParams));

    /* Set default values to the arguments */
    params->nbmode = 1;
    params->tRef = 0.;
    params->phiRef = 0.;
    params->fRef = 0.;
    params->m1 = 1. * 1e6 * MSUN_SI;
    params->m2 = 1. * 1e6 * MSUN_SI;
    params->distance = 1. * 1e9 * PC_SI;
    params->inclination = 0.;
    params->lambda = 0.;
    params->beta = 0.;
    params->psi = 0.;
    sprintf(params->outname, "");

    /* consume command line */
    for (i = 1; i < argc; ++i) {
        if (strcmp(argv[i], "--nbmode") == 0) {
            params->nbmode = atof(argv[++i]);
        } else if (strcmp(argv[i], "--tRef") == 0) {
            params->tRef = atof(argv[++i]);
        } else if (strcmp(argv[i], "--phiRef") == 0) {
            params->phiRef = atof(argv[++i]);
        } else if (strcmp(argv[i], "--fRef") == 0) {
            params->fRef = atof(argv[++i]);
        } else if (strcmp(argv[i], "--m1") == 0) {
            params->m1 = atof(argv[++i]) * MSUN_SI;
        } else if (strcmp(argv[i], "--m2") == 0) {
            params->m2 = atof(argv[++i]) * MSUN_SI;
        } else if (strcmp(argv[i], "--distance") == 0) {
            params->distance = atof(argv[++i]) * 1e6 * PC_SI;
        } else if (strcmp(argv[i], "--inclination") == 0) {
            params->inclination = atof(argv[++i]);
        } else if (strcmp(argv[i], "--lambda") == 0) {
            params->lambda = atof(argv[++i]);
        } else if (strcmp(argv[i], "--beta") == 0) {
            params->beta = atof(argv[++i]);
        } else if (strcmp(argv[i], "--psi") == 0) {
            params->psi = atof(argv[++i]);
        } else if (strcmp(argv[i], "--outname") == 0) {
            strncpy(params->outname, argv[++i], 256);
        } else {
            printf("Error: invalid option: %s\n", argv[i]);
            goto fail;
        }
    }

    return params;

    fail:
    free(params);
    exit(1);
}

/* Function to output to a file an AmpPhaseFrequencySeries; each mode will be output to a separate file using this function */
static int Write_CAmpPhaseFrequencySeries(FILE* f, CAmpPhaseFrequencySeries* freqseries) {
    gsl_vector* freq = freqseries->freq;
    gsl_vector* amp_real = freqseries->amp_real;
    gsl_vector* amp_imag = freqseries->amp_imag;
    gsl_vector* phase = freqseries->phase;

    int len = (int) freq->size;
    /*Here, we could add a check on the length of the gsl_vectors*/

    fprintf(f, "# f amp_re amp_im phase\n");
    for (int i=0; i<len; i++) {
      fprintf(f, "%.16e %.16e %.16e %.16e\n", gsl_vector_get(freq, i), gsl_vector_get(amp_real, i), gsl_vector_get(amp_imag, i), gsl_vector_get(phase, i));
    }

    return 0;
}

/*
 * Swhitenoise
 * a simple noise function
 */
double Swhitenoise(double x){
  return 1;
};

/*
 * main
 */
int main (int argc , char **argv) {
    FILE* f;
    ListmodesCAmpPhaseFrequencySeries* listROM = NULL;
    ListmodesCAmpPhaseFrequencySeries* listA = NULL;
    ListmodesCAmpPhaseFrequencySeries* listE = NULL;
    ListmodesCAmpPhaseFrequencySeries* listT = NULL;
    //ROMParams* params;
    LISAParams* params;

    /* parse commandline */
    //params = parse_args_ROM(argc, argv);
    params = parse_args_LISA(argc, argv);

    /* Generate the waveform with the ROM */
    SimEOBNRv2HMROM(&listROM, params->nbmode, params->tRef, params->phiRef, params->fRef, params->m1, params->m2, params->distance);
    /* Process the waveform through the LISA response */
    LISASimFDResponseTDI(&listROM, &listA, &listE, &listT, params->inclination, params->lambda, params->beta, params->psi);

    ListmodesCAmpPhaseFrequencySeries* listelementA;
    ListmodesCAmpPhaseFrequencySeries* listelementE;
    ListmodesCAmpPhaseFrequencySeries* listelementT;
    int l,m;
    int status = 0;
    /* Loop over the modes */
    for(int i=0; i<params->nbmode; i++){
      l = listmode[i][0];
      m = listmode[i][1];
      listelementA = ListmodesCAmpPhaseFrequencySeries_GetMode(listA, l, m);
      listelementE = ListmodesCAmpPhaseFrequencySeries_GetMode(listE, l, m);
      listelementT = ListmodesCAmpPhaseFrequencySeries_GetMode(listT, l, m);
      /* Write files - suffix _A,E,T_lm.dat imposed to each file name, attached to the string given by outname */
      /* If outname is still the default empty string, we do not output and skip this stage */
      if(!(strcmp(params->outname, "") == 0)){
	char *filenameA = malloc(strlen(params->outname)+64);
	char *filenameE = malloc(strlen(params->outname)+64);
	char *filenameT = malloc(strlen(params->outname)+64);
	sprintf(filenameA, "%s%s%d%d%s", params->outname, "_A_", l, m, ".dat");
	sprintf(filenameE, "%s%s%d%d%s", params->outname, "_E_", l, m, ".dat");
	sprintf(filenameT, "%s%s%d%d%s", params->outname, "_T_", l, m, ".dat");
	f = fopen(filenameA, "w");
	status |= Write_CAmpPhaseFrequencySeries(f, listelementA->freqseries);
	fclose(f);
	f = fopen(filenameE, "w");
	status |= Write_CAmpPhaseFrequencySeries(f, listelementE->freqseries);
	fclose(f);
	f = fopen(filenameT, "w");
	status |= Write_CAmpPhaseFrequencySeries(f, listelementT->freqseries);
	fclose(f);
	if (status) goto fail;
      }

      /* Example SNR calculation, taking the A observable */
      double *f1=listelementA->freqseries->freq->data;
      int n1=listelementA->freqseries->freq->size;
      double *s1Ar=listelementA->freqseries->amp_real->data;
      double *s1Ai=listelementA->freqseries->amp_imag->data;
      double *s1p=listelementA->freqseries->phase->data;
      double *f2=listelementA->freqseries->freq->data;
      int n2=listelementA->freqseries->freq->size;
      double *s2Ar=listelementA->freqseries->amp_real->data;
      double *s2Ai=listelementA->freqseries->amp_imag->data;
      double *s2p=listelementA->freqseries->phase->data;

      printf("n1: %d\n", n1);
      printf("s1Ar[299]: %g\n", s1Ar[299]);

      double start=((double)clock())/CLOCKS_PER_SEC;
      //JGB: Set these somehwere else where it makes sense.  f_min or f_max <= 0 means use intersection of signal domains.
      double f_min=-1.0;
      double f_max=-1.0;
      double rho2= wip_phase (f1, n1, f2, n2, s1Ar, s1Ai, s1p, s2Ar, s2Ai, s2p, Swhitenoise, 1.0, f_min, f_max);
      double end=((double)clock())/CLOCKS_PER_SEC;
      printf( "SNR2 = %g,   SNR time = %g\n", rho2, end-start);
      printf( "SNR = %g,   SNR time = %g\n",sqrt(rho2), end-start);

    }

    /* clean up */
    free(params);
    ListmodesCAmpPhaseFrequencySeries_Destroy(listROM);
    ListmodesCAmpPhaseFrequencySeries_Destroy(listA);
    ListmodesCAmpPhaseFrequencySeries_Destroy(listE);
    ListmodesCAmpPhaseFrequencySeries_Destroy(listT);
    return 0;

    fail:
    free(params);
    ListmodesCAmpPhaseFrequencySeries_Destroy(listROM);
    ListmodesCAmpPhaseFrequencySeries_Destroy(listA);
    ListmodesCAmpPhaseFrequencySeries_Destroy(listE);
    ListmodesCAmpPhaseFrequencySeries_Destroy(listT);
    return 1;
}


#include "../precision.h"

#undef SKIP

#ifdef GSL_FLOAT
#ifdef MATLAB_FLOAT_H
#define SKIP
#else
#define MATLAB_FLOAT_H
#endif
#endif

#ifdef GSL_DOUBLE
#ifdef MATLAB_H
#define SKIP
#else
#define MATLAB_H
#endif
#endif

#ifdef GSL_LONG_DOUBLE
#ifdef MATLAB_LONG_DOUBLE_H
#define SKIP
#else
#define MATLAB_LONG_DOUBLE_H
#endif
#endif

#ifndef SKIP

#include <gsl/gsl_matrix.h>
#include <gsl/gsl_permutation.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_vector.h>
#include <limits>
#include <string>

namespace MATLAB_NAMESPACE {

	// Functions
	VECTOR_T* abs(const VECTOR_T* v);
	MATRIX_T* abs(const MATRIX_T* m);
	int all(const VECTOR_T* v);
	VECTOR_T* all(const MATRIX_T* m, int dim = 1);
	int any(const VECTOR_T* v);
	VECTOR_T* any(const MATRIX_T* m, int dim = 1);
	std::string dec2bin(int n);
	std::string dec2bin(int n, int len);
	MATRIX_T* diag(const VECTOR_T* v, int k = 0);
	VECTOR_T* diag(const MATRIX_T* m, int k = 0);
	MATRIX_T* eye(int size);
	MATRIX_T* eye(int size1, int size2);
	VECTOR_T* find(const VECTOR_T* v, int n = std::numeric_limits<int>::max(), const std::string& direction = "first");
	VECTOR_T* find(const MATRIX_T* m, int n = std::numeric_limits<int>::max(), const std::string& direction = "first");
	MATRIX_T* find_ij(const MATRIX_T* m, int n = std::numeric_limits<int>::max(), const std::string& direction = "first");
	VECTOR_T* hist(const VECTOR_T* v, int n = 10);
	VECTOR_T* hist(const VECTOR_T* v, const VECTOR_T* centers);
	MATRIX_T* inv(const MATRIX_T* m);
	int length(const VECTOR_T* v);
	int length(const MATRIX_T* m);
	FP_T max(FP_T x, FP_T y);
	FP_T max(const VECTOR_T* v);
	VECTOR_T* max(const MATRIX_T* m, int dim = 1);
	FP_T mean(const VECTOR_T* v, const std::string& opt = "a");
	VECTOR_T* mean(const MATRIX_T* m, int dim = 1, const std::string& opt = "a");
	FP_T min(FP_T x, FP_T y);
	FP_T min(const VECTOR_T* v);
	VECTOR_T* min(const MATRIX_T* m, int dim = 1);
	int nnz(const VECTOR_T* v);
	int nnz(const MATRIX_T* m);
	VECTOR_T* nonzeros(const MATRIX_T* m);
	FP_T norm(const VECTOR_T* v, int p);
	VECTOR_T* normpdf(const VECTOR_T* v, FP_T mean, FP_T stdev);
	MATRIX_T* ones(int size);
	MATRIX_T* ones(int size1, int size2);
	VECTOR_T* ones_vector(int size);
	FP_T prod(const VECTOR_T* v);
	VECTOR_T* prod(const MATRIX_T* m, int dim = 1);
	MATRIX_T* rand(int size);
	MATRIX_T* rand(int size1, int size2);
	VECTOR_T* rand_vector(int size);
	gsl_permutation* randperm(int size);
	VECTOR_T* reverse(const VECTOR_T* v);
	VECTOR_T* setxor(const VECTOR_T* v1, const VECTOR_T* v2);
	VECTOR_T* sort(const VECTOR_T* v, const std::string& mode = "ascend", VECTOR_T** ind = NULL);
	MATRIX_T* sort(const MATRIX_T* m, int dim = 1, const std::string& mode = "ascend", MATRIX_T** ind = NULL);
	VECTOR_T* sortrows(const VECTOR_T* v, VECTOR_T** ind = NULL);
	MATRIX_T* sortrows(const MATRIX_T* m, VECTOR_T** ind = NULL);
	FP_T std(const VECTOR_T* v, int opt = 0);
	VECTOR_T* std(const MATRIX_T* m, int opt = 0, int dim = 1);
	FP_T sum(const VECTOR_T* v);
	VECTOR_T* sum(const MATRIX_T* m, int dim = 1);
	MATRIX_T* toeplitz(const VECTOR_T* column, const VECTOR_T* row = NULL);
	MATRIX_T* tril(const MATRIX_T* m, int k = 0);
	MATRIX_T* triu(const MATRIX_T* m, int k = 0);
	VECTOR_T* unique(const VECTOR_T* v, const std::string& first_or_last = "last", VECTOR_T** i = NULL, VECTOR_T** j = NULL);
	VECTOR_T* unique(const MATRIX_T* m, const std::string& first_or_last = "last", VECTOR_T** i = NULL, VECTOR_T** j = NULL);
	MATRIX_T* unique_rows(const MATRIX_T* m, const std::string& first_or_last = "last", VECTOR_T** i = NULL, VECTOR_T** j = NULL);
	MATRIX_T* zeros(int size);
	MATRIX_T* zeros(int size1, int size2);
	VECTOR_T* zeros_vector(int size);

	// Operators
	VECTOR_T* concatenate(const VECTOR_T* v, FP_T x);
	VECTOR_T* concatenate(FP_T x, const VECTOR_T* v);
	VECTOR_T* concatenate(const VECTOR_T* v1, const VECTOR_T* v2);
	MATRIX_T* concatenate_columns(const VECTOR_T* v1, const VECTOR_T* v2);
	MATRIX_T* concatenate_columns(const MATRIX_T* m, const VECTOR_T* v);
	MATRIX_T* concatenate_columns(const VECTOR_T* v, const MATRIX_T* m);
	MATRIX_T* concatenate_columns(const MATRIX_T* m1, const MATRIX_T* m2);
	MATRIX_T* concatenate_rows(const VECTOR_T* v1, const VECTOR_T* v2);
	MATRIX_T* concatenate_rows(const MATRIX_T* m, const VECTOR_T* v);
	MATRIX_T* concatenate_rows(const VECTOR_T* v, const MATRIX_T* m);
	MATRIX_T* concatenate_rows(const MATRIX_T* m1, const MATRIX_T* m2);
	VECTOR_T* copy(const VECTOR_T* v);
	MATRIX_T* copy(const MATRIX_T* m);
	MATRIX_T* div_left(const MATRIX_T* m1, const MATRIX_T* m2);
	MATRIX_T* div_right(const MATRIX_T* m1, const MATRIX_T* m2);
	VECTOR_T* logical_and(const VECTOR_T* v1, const VECTOR_T* v2);
	MATRIX_T* logical_and(const MATRIX_T* m1, const MATRIX_T* m2);
	VECTOR_T* logical_not(const VECTOR_T* v);
	MATRIX_T* logical_not(const MATRIX_T* m);
	VECTOR_T* logical_or(const VECTOR_T* v1, const VECTOR_T* v2);
	MATRIX_T* logical_or(const MATRIX_T* m1, const MATRIX_T* m2);
	MATRIX_T* mul(const MATRIX_T* m1, const MATRIX_T* m2);
	MATRIX_T* pow(const MATRIX_T* m, int power);
	VECTOR_T* pow_elements(const VECTOR_T* v, FP_T power);
	VECTOR_T* pow_elements(const VECTOR_T* v, const VECTOR_T* powers);
	MATRIX_T* pow_elements(const MATRIX_T* m, FP_T power);
	MATRIX_T* pow_elements(const MATRIX_T* m, const MATRIX_T* powers);
	VECTOR_T* sequence(int start, int end);
	VECTOR_T* sequence(int start, int step, int end);

	// Floating-point comparison
	extern FP_T epsilon;
	int fp_compare(FP_T x, FP_T y);
	bool fp_zero(FP_T x);
	bool fp_nonzero(FP_T x);
	bool fp_equal(FP_T x, FP_T y);
	bool fp_not_equal(FP_T x, FP_T y);
	bool fp_less(FP_T x, FP_T y);
	bool fp_less_or_equal(FP_T x, FP_T y);
	bool fp_greater(FP_T x, FP_T y);
	bool fp_greater_or_equal(FP_T x, FP_T y);

	// Vector/matrix comparison
	typedef bool (*comparator)(FP_T, FP_T);
	int compare_vectors(const VECTOR_T* v1, const VECTOR_T* v2);
	bool vector_less(VECTOR_T* v1, VECTOR_T* v2);
	int compare_matrices(const MATRIX_T* m1, const MATRIX_T* m2);
	bool matrix_less(MATRIX_T* m1, MATRIX_T* m2);
	VECTOR_T* compare_elements(const VECTOR_T* v, comparator compare, FP_T x);
	VECTOR_T* compare_elements(const VECTOR_T* v1, comparator compare, const VECTOR_T* v2);
	MATRIX_T* compare_elements(const MATRIX_T* m, comparator compare, FP_T x);
	MATRIX_T* compare_elements(const MATRIX_T* m1, comparator compare, const MATRIX_T* m2);

	// Vector-by-vector indexing
	VECTOR_T* ordinal_index(const VECTOR_T* v, const VECTOR_T* indices);
	void ordinal_index_assign(VECTOR_T* v, const VECTOR_T* indices, FP_T value);
	void ordinal_index_assign(VECTOR_T* v, const VECTOR_T* indices, const VECTOR_T* values);
	VECTOR_T* logical_index(const VECTOR_T* v, const VECTOR_T* logical_v);
	void logical_index_assign(VECTOR_T* v, const VECTOR_T* logical_v, FP_T value);
	void logical_index_assign(VECTOR_T* v, const VECTOR_T* logical_v, const VECTOR_T* values);

	// Matrix-by-integer indexing
	FP_T ordinal_index(const MATRIX_T* m, int index);
	void ordinal_index_assign(MATRIX_T* m, int index, FP_T value);

	// Matrix-by-vector indexing
	VECTOR_T* ordinal_index(const MATRIX_T* m, const VECTOR_T* indices);
	void ordinal_index_assign(MATRIX_T* m, const VECTOR_T* indices, FP_T value);
	void ordinal_index_assign(MATRIX_T* m, const VECTOR_T* indices, const VECTOR_T* values);
	VECTOR_T* logical_index(const MATRIX_T* m, const VECTOR_T* logical_v);
	void logical_index_assign(MATRIX_T* m, const VECTOR_T* logical_v, FP_T value);
	void logical_index_assign(MATRIX_T* m, const VECTOR_T* logical_v, const VECTOR_T* values);

	// Matrix-by-two-vectors indexing (non-mixed)
	MATRIX_T* ordinal_index(const MATRIX_T* m, const VECTOR_T* rows, const VECTOR_T* columns);
	void ordinal_index_assign(MATRIX_T* m, const VECTOR_T* rows, const VECTOR_T* columns, FP_T value);
	void ordinal_index_assign(MATRIX_T* m, const VECTOR_T* rows, const VECTOR_T* columns, const MATRIX_T* values);
	MATRIX_T* logical_index(const MATRIX_T* m, const VECTOR_T* logical_rows, const VECTOR_T* logical_columns);
	void logical_index_assign(MATRIX_T* m, const VECTOR_T* logical_rows, const VECTOR_T* logical_columns, FP_T value);
	void logical_index_assign(MATRIX_T* m, const VECTOR_T* logical_rows, const VECTOR_T* logical_columns, const MATRIX_T* values);

	// Matrix-by-two-vectors indexing (mixed)
	MATRIX_T* ord_log_index(const MATRIX_T* m, const VECTOR_T* rows, const VECTOR_T* logical_columns);
	void ord_log_index_assign(MATRIX_T* m, const VECTOR_T* rows, const VECTOR_T* logical_columns, FP_T value);
	void ord_log_index_assign(MATRIX_T* m, const VECTOR_T* rows, const VECTOR_T* logical_columns, const MATRIX_T* values);
	MATRIX_T* log_ord_index(const MATRIX_T* m, const VECTOR_T* logical_rows, const VECTOR_T* columns);
	void log_ord_index_assign(MATRIX_T* m, const VECTOR_T* logical_rows, const VECTOR_T* columns, FP_T value);
	void log_ord_index_assign(MATRIX_T* m, const VECTOR_T* logical_rows, const VECTOR_T* columns, const MATRIX_T* values);

	// Matrix-by-matrix indexing
	MATRIX_T* ordinal_index(const MATRIX_T* m, const MATRIX_T* indices);
	void ordinal_index_assign(MATRIX_T* m, const MATRIX_T* indices, FP_T value);
	void ordinal_index_assign(MATRIX_T* m, const MATRIX_T* indices, const MATRIX_T* values);
	VECTOR_T* logical_index(const MATRIX_T* m, const MATRIX_T* logical_m);
	void logical_index_assign(MATRIX_T* m, const MATRIX_T* logical_m, FP_T value);
	void logical_index_assign(MATRIX_T* m, const MATRIX_T* logical_m, const VECTOR_T* values);

	// Vector/matrix conversion
	void to_array(const VECTOR_T* v, FP_T* array);
	bool to_bool(const VECTOR_T* v);
	bool to_bool(const MATRIX_T* m);
	VECTOR_T* to_vector(const gsl_vector* v_d);
	gsl_vector* to_vector_double(const VECTOR_T* v);
	VECTOR_T* to_vector(const MATRIX_T* m);
	MATRIX_T* to_column_matrix(const VECTOR_T* v);
	MATRIX_T* to_row_matrix(const VECTOR_T* v);
	MATRIX_T* to_matrix(const gsl_matrix* m_d);
	gsl_matrix* to_matrix_double(const MATRIX_T* m);
	VECTOR_T* to_vector(const gsl_permutation* p);
	gsl_permutation* to_permutation(const VECTOR_T* v);

	// Utility
	gsl_rng* get_rng();
	void seed_rng(const gsl_rng* rng, unsigned long seed);
	VECTOR_T* permute(const gsl_permutation* p, const VECTOR_T* v);
	MATRIX_T* permute_columns(const gsl_permutation* p, const MATRIX_T* m);
	MATRIX_T* permute_rows(const gsl_permutation* p, const MATRIX_T* m);
}

#endif