//====---- Sudoku/Board_Iterators.h                                   ----====//
//
// Location aware iterator implementation for Board
//====--------------------------------------------------------------------====//
//
// The iterator can return a Location object.
// included by Board.h
//
//====--------------------------------------------------------------------====//
#pragma once

#include "Sudoku/Location.h"
#include <gsl/gsl>

#include <iterator>
#include <limits>
#include <type_traits>

#include "Board.fwd.h" // Forward declarations

#include <cassert>

namespace Sudoku
{
template<typename T, int N, bool is_const = false, bool is_reverse = false>
class Board_iterator;

//====--- Aliases --------------------------------------------------------====//
template<typename T, int N>
using const_Board_iterator = Board_iterator<T, N, true, false>;
template<typename T, int N>
using reverse_Board_iterator = Board_iterator<T, N, false, true>;
template<typename T, int N>
using const_reverse_Board_iterator = Board_iterator<T, N, true, true>;

//====--------------------------------------------------------------------====//
template<typename T, int N, bool is_const, bool is_reverse>
class Board_iterator
{
	using owner_type =
		std::conditional_t<is_const, Board<T, N> const, Board<T, N>>;
	using Location = ::Sudoku::Location<N>;

public:
	// member types
	using difference_type = std::ptrdiff_t;
	static_assert(
		full_size<N> < std::numeric_limits<difference_type>::max(),
		"Use std::ptrdiff_t for Board_iterator::difference_type.");
	using value_type        = T;
	using pointer           = std::conditional_t<is_const, T const*, T*>;
	using reference         = std::conditional_t<is_const, T const&, T&>;
	using iterator_category = std::random_access_iterator_tag;

	// Constructors
	constexpr Board_iterator() noexcept
	{ // defaults to [r]begin()
		if constexpr (is_reverse)
			elem_ = full_size<N> - 1;
	}
	explicit constexpr Board_iterator(gsl::not_null<owner_type*> owner) noexcept
		: board_(owner)
	{ // defaults to [r]begin()
		if constexpr (is_reverse)
			elem_ = (full_size<N> - 1);
	}
	explicit constexpr Board_iterator(
		gsl::not_null<owner_type*> owner, Location loc) noexcept
		: board_(owner), elem_(loc.element())
	{
	}
	// Assignment
	constexpr Board_iterator& operator=(Location const loc) noexcept
	{
		assert(is_valid(loc));
		elem_ = loc.element();
		return (*this);
	}

	// [[implicit]] Type Conversion to const_(reverse_)iterator
	// NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
	constexpr operator Board_iterator<T, N, true, is_reverse>() const noexcept
	{
		static_assert(!is_const);
		return Board_iterator<T, N, true, is_reverse>(
			gsl::not_null<owner_type*>{board_}, Location{elem_});
	}

	//====----------------------------------------------------------------====//
	[[nodiscard]] constexpr Location location() const noexcept
	{
		return Location{gsl::narrow_cast<int>(elem_)};
	}
	// Allows for conversion to Location
	explicit constexpr operator Location() const noexcept { return location(); }

	//====----------------------------------------------------------------====//
	[[nodiscard]] constexpr reference operator*() const noexcept
	{ // Only valid in a dereferenceable location:
		assert(board_ != nullptr && is_valid(Location{elem_}));
		return board_->operator[](location());
	}
	[[nodiscard]] constexpr pointer operator->() const noexcept
	{ // member access; equivalent to (*p).member
		return std::pointer_traits<pointer>::pointer_to(**this);
	}

	//====----------------------------------------------------------------====//
	constexpr Board_iterator& operator++() noexcept;
	constexpr Board_iterator operator++(int) noexcept;
	constexpr Board_iterator& operator--() noexcept;
	constexpr Board_iterator operator--(int) noexcept;
	constexpr Board_iterator& operator+=(const difference_type offset) noexcept;
	constexpr Board_iterator& operator-=(const difference_type offset) noexcept;

	[[nodiscard]] friend constexpr difference_type operator-(
		Board_iterator const left, Board_iterator const right) noexcept
	{ // difference
		assert(is_same_Board(left, right));
		if constexpr (is_reverse)
			return right.elem_ - left.elem_;
		else
			return left.elem_ - right.elem_;
	}

	[[nodiscard]] constexpr reference
		operator[](const difference_type offset) const noexcept
	{
		return (*(*this + offset));
	}

	//====----------------------------------------------------------------====//
	[[nodiscard]] friend constexpr bool operator==(
		Board_iterator const left, Board_iterator const right) noexcept
	{
		assert(is_same_Board(left, right));
		return is_same_Board(left, right) && left.elem_ == right.elem_;
	}
	[[nodiscard]] friend constexpr bool operator<(
		Board_iterator const left, Board_iterator const right) noexcept
	{
		assert(is_same_Board(left, right));
		if constexpr (is_reverse)
			return left.elem_ > right.elem_;
		else
			return left.elem_ < right.elem_;
	}

private:
	owner_type* board_{nullptr};
	gsl::index elem_{0};

	friend constexpr bool
		is_same_Board(Board_iterator const A, Board_iterator const B) noexcept
	{ // compare address of:
		return A.board_ == B.board_;
	}
};

//====--------------------------------------------------------------------====//
template<typename T, int N, bool C, bool is_reverse>
constexpr Board_iterator<T, N, C, is_reverse>&
	Board_iterator<T, N, C, is_reverse>::operator++() noexcept
{ // pre-increment
	assert(board_ != nullptr);
	if constexpr (is_reverse)
	{
		assert(elem_ >= 0);
		--elem_;
	}
	else
	{
		assert(elem_ < full_size<N>);
		++elem_;
	}
	return (*this);
}
template<typename T, int N, bool C, bool R>
constexpr Board_iterator<T, N, C, R> // NOLINTNEXTLINE(readability/casting)
	Board_iterator<T, N, C, R>::operator++(int) noexcept
{ // post-increment
	const Board_iterator pre{*this};
	operator++();
	return pre;
}
template<typename T, int N, bool C, bool is_reverse>
constexpr Board_iterator<T, N, C, is_reverse>&
	Board_iterator<T, N, C, is_reverse>::operator--() noexcept
{ // pre-decrement
	assert(board_ != nullptr);
	if constexpr (is_reverse)
	{
		assert(elem_ < full_size<N> - 1);
		++elem_;
	}
	else
	{
		assert(elem_ > 0);
		--elem_;
	}
	return (*this);
}
template<typename T, int N, bool C, bool R>
constexpr Board_iterator<T, N, C, R> // NOLINTNEXTLINE(readability/casting)
	Board_iterator<T, N, C, R>::operator--(int) noexcept
{ // post-decrement
	const Board_iterator pre{*this};
	operator--();
	return pre;
}
template<typename T, int N, bool C, bool is_reverse>
constexpr Board_iterator<T, N, C, is_reverse>&
	Board_iterator<T, N, C, is_reverse>::operator+=(
		const difference_type offset) noexcept
{
	assert(offset == 0 || board_ != nullptr);
	if constexpr (is_reverse)
	{
		elem_ -= offset;
		assert(elem_ >= -1);
		assert(elem_ < full_size<N>);
	}
	else
	{
		elem_ += offset;
		assert(elem_ >= 0);
		assert(elem_ <= full_size<N>);
	}
	return (*this);
}
template<typename T, int N, bool C, bool is_reverse>
constexpr Board_iterator<T, N, C, is_reverse>&
	Board_iterator<T, N, C, is_reverse>::operator-=(
		const difference_type offset) noexcept
{
	return operator+=(-offset);
}

//====--------------------------------------------------------------------====//
// Free functions (non-friend)
//====--------------------------------------------------------------------====//
template<typename T, int N, bool is_const, bool is_reverse>
[[nodiscard]] constexpr auto operator+(
	Board_iterator<T, N, is_const, is_reverse> left,
	typename Board_iterator<T, N, is_const, is_reverse>::difference_type const
		offset) noexcept
{ // itr + offset
	return (left += offset);
}
template<typename T, int N, bool is_const, bool is_reverse>
[[nodiscard]] inline constexpr auto operator+(
	typename Board_iterator<T, N, is_const, is_reverse>::difference_type const
		offset,
	Board_iterator<T, N, is_const, is_reverse> itr) noexcept
{ // offset + itr
	return (itr += offset);
}
template<typename T, int N, bool is_const, bool is_reverse>
[[nodiscard]] constexpr auto operator-(
	Board_iterator<T, N, is_const, is_reverse> left,
	typename Board_iterator<T, N, is_const, is_reverse>::difference_type const
		offset) noexcept
{ // itr - offset
	return (left += -offset);
}

template<typename T, int N, bool is_const, bool is_reverse>
[[nodiscard]] inline constexpr bool operator!=(
	Board_iterator<T, N, is_const, is_reverse> const left,
	Board_iterator<T, N, is_const, is_reverse> const right) noexcept
{
	return !(left == right);
}
template<typename T, int N, bool is_const, bool is_reverse>
[[nodiscard]] inline constexpr bool operator>(
	Board_iterator<T, N, is_const, is_reverse> const left,
	Board_iterator<T, N, is_const, is_reverse> const right) noexcept
{
	return (right < left);
}
template<typename T, int N, bool is_const, bool is_reverse>
[[nodiscard]] inline constexpr bool operator<=(
	Board_iterator<T, N, is_const, is_reverse> const left,
	Board_iterator<T, N, is_const, is_reverse> const right) noexcept
{
	return !(right < left);
}
template<typename T, int N, bool is_const, bool is_reverse>
[[nodiscard]] inline constexpr bool operator>=(
	Board_iterator<T, N, is_const, is_reverse> const left,
	Board_iterator<T, N, is_const, is_reverse> const right) noexcept
{
	return !(left < right);
}

} // namespace Sudoku


// Copyright (c) 2017, Lawrence Livermore National Security, LLC. Produced at
// the Lawrence Livermore National Laboratory. LLNL-CODE-734707. All Rights
// reserved. See files LICENSE and NOTICE for details.
//
// This file is part of CEED, a collection of benchmarks, miniapps, software
// libraries and APIs for efficient high-order finite element and spectral
// element discretizations for exascale applications. For more information and
// source code availability see http://github.com/ceed.
//
// The CEED research is supported by the Exascale Computing Project 17-SC-20-SC,
// a collaborative effort of two U.S. Department of Energy organizations (Office
// of Science and the National Nuclear Security Administration) responsible for
// the planning and preparation of a capable exascale ecosystem, including
// software, applications, hardware, advanced system engineering and early
// testbed platforms, in support of the nation's exascale computing imperative.

//                        libCEED + PETSc Example: CEED BPs
//
// This example demonstrates a simple usage of libCEED with PETSc to solve the
// CEED BP benchmark problems, see http://ceed.exascaleproject.org/bps.
//
// The code uses higher level communication protocols in DMPlex.
//
// Build with:
//
//     make bps [PETSC_DIR=</path/to/petsc>] [CEED_DIR=</path/to/libceed>]
//
// Sample runs:
//
//     ./bps -problem bp1 -degree 3
//     ./bps -problem bp2 -degree 3
//     ./bps -problem bp3 -degree 3
//     ./bps -problem bp4 -degree 3
//     ./bps -problem bp5 -degree 3 -ceed /cpu/self
//     ./bps -problem bp6 -degree 3 -ceed /gpu/cuda
//
//TESTARGS -ceed {ceed_resource} -test -problem bp5 -degree 3 -ksp_max_it_clip 15,15

/// @file
/// CEED BPs example using PETSc with DMPlex
/// See bpsraw.c for a "raw" implementation using a structured grid.
const char help[] = "Solve CEED BPs using PETSc with DMPlex\n";

#include <stdbool.h>
#include <string.h>
#include <ceed.h>
#include <petsc.h>
#include <petscdmplex.h>
#include <petscksp.h>
#include <petscsys.h>

#include "bps.h"
#include "include/bpsproblemdata.h"
#include "include/petscmacros.h"
#include "include/petscutils.h"
#include "include/matops.h"
#include "include/structs.h"
#include "include/libceedsetup.h"

#if PETSC_VERSION_LT(3,12,0)
#ifdef PETSC_HAVE_CUDA
#include <petsccuda.h>
// Note: With PETSc prior to version 3.12.0, providing the source path to
//       include 'cublas_v2.h' will be needed to use 'petsccuda.h'.
#endif
#endif

// -----------------------------------------------------------------------------
// Utilities
// -----------------------------------------------------------------------------

// Utility function, compute three factors of an integer
static void Split3(PetscInt size, PetscInt m[3], bool reverse) {
  for (PetscInt d=0, size_left=size; d<3; d++) {
    PetscInt try = (PetscInt)PetscCeilReal(PetscPowReal(size_left, 1./(3 - d)));
    while (try * (size_left / try) != size_left) try++;
    m[reverse ? 2-d : d] = try;
    size_left /= try;
  }
}

static int Max3(const PetscInt a[3]) {
  return PetscMax(a[0], PetscMax(a[1], a[2]));
}

static int Min3(const PetscInt a[3]) {
  return PetscMin(a[0], PetscMin(a[1], a[2]));
}

// -----------------------------------------------------------------------------
// Parameter structure for running problems
// -----------------------------------------------------------------------------
typedef struct RunParams_ *RunParams;
struct RunParams_ {
  MPI_Comm comm;
  PetscBool test_mode, read_mesh, user_l_nodes, write_solution;
  char *filename, *hostname;
  PetscInt local_nodes, degree, q_extra, dim, num_comp_u, *mesh_elem;
  PetscInt ksp_max_it_clip[2];
  PetscMPIInt ranks_per_node;
  BPType bp_choice;
  PetscLogStage solve_stage;
};

// -----------------------------------------------------------------------------
// Main body of program, called in a loop for performance benchmarking purposes
// -----------------------------------------------------------------------------
static PetscErrorCode RunWithDM(RunParams rp, DM dm,
                                const char *ceed_resource) {
  PetscErrorCode ierr;
  double my_rt_start, my_rt, rt_min, rt_max;
  PetscInt xl_size, l_size, g_size;
  PetscScalar *r;
  Vec X, X_loc, rhs, rhs_loc;
  Mat mat_O;
  KSP ksp;
  UserO user_O;
  Ceed ceed;
  CeedData ceed_data;
  CeedQFunction qf_error;
  CeedOperator op_error;
  CeedVector rhs_ceed, target;
  VecType vec_type;
  PetscMemType mem_type;

  PetscFunctionBeginUser;
  // Set up libCEED
  CeedInit(ceed_resource, &ceed);
  CeedMemType mem_type_backend;
  CeedGetPreferredMemType(ceed, &mem_type_backend);

  ierr = DMGetVecType(dm, &vec_type); CHKERRQ(ierr);
  if (!vec_type) { // Not yet set by user -dm_vec_type
    switch (mem_type_backend) {
    case CEED_MEM_HOST: vec_type = VECSTANDARD; break;
    case CEED_MEM_DEVICE: {
      const char *resolved;
      CeedGetResource(ceed, &resolved);
      if (strstr(resolved, "/gpu/cuda")) vec_type = VECCUDA;
      else if (strstr(resolved, "/gpu/hip/occa"))
        vec_type = VECSTANDARD; // https://github.com/CEED/libCEED/issues/678
      else if (strstr(resolved, "/gpu/hip")) vec_type = VECHIP;
      else vec_type = VECSTANDARD;
    }
    }
    ierr = DMSetVecType(dm, vec_type); CHKERRQ(ierr);
  }

  // Create global and local solution vectors
  ierr = DMCreateGlobalVector(dm, &X); CHKERRQ(ierr);
  ierr = VecGetLocalSize(X, &l_size); CHKERRQ(ierr);
  ierr = VecGetSize(X, &g_size); CHKERRQ(ierr);
  ierr = DMCreateLocalVector(dm, &X_loc); CHKERRQ(ierr);
  ierr = VecGetSize(X_loc, &xl_size); CHKERRQ(ierr);
  ierr = VecDuplicate(X, &rhs); CHKERRQ(ierr);

  // Operator
  ierr = PetscMalloc1(1, &user_O); CHKERRQ(ierr);
  ierr = MatCreateShell(rp->comm, l_size, l_size, g_size, g_size,
                        user_O, &mat_O); CHKERRQ(ierr);
  ierr = MatShellSetOperation(mat_O, MATOP_MULT,
                              (void(*)(void))MatMult_Ceed); CHKERRQ(ierr);
  ierr = MatShellSetOperation(mat_O, MATOP_GET_DIAGONAL,
                              (void(*)(void))MatGetDiag); CHKERRQ(ierr);
  ierr = MatShellSetVecType(mat_O, vec_type); CHKERRQ(ierr);

  // Print summary
  if (!rp->test_mode) {
    PetscInt P = rp->degree + 1, Q = P + rp->q_extra;

    const char *used_resource;
    CeedGetResource(ceed, &used_resource);

    VecType vec_type;
    ierr = VecGetType(X, &vec_type); CHKERRQ(ierr);

    PetscInt c_start, c_end;
    ierr = DMPlexGetHeightStratum(dm, 0, &c_start, &c_end); CHKERRQ(ierr);
    PetscMPIInt comm_size;
    ierr = MPI_Comm_size(rp->comm, &comm_size); CHKERRQ(ierr);
    ierr = PetscPrintf(rp->comm,
                       "\n-- CEED Benchmark Problem %d -- libCEED + PETSc --\n"
                       "  MPI:\n"
                       "    Hostname                           : %s\n"
                       "    Total ranks                        : %d\n"
                       "    Ranks per compute node             : %d\n"
                       "  PETSc:\n"
                       "    PETSc Vec Type                     : %s\n"
                       "  libCEED:\n"
                       "    libCEED Backend                    : %s\n"
                       "    libCEED Backend MemType            : %s\n"
                       "  Mesh:\n"
                       "    Number of 1D Basis Nodes (P)       : %d\n"
                       "    Number of 1D Quadrature Points (Q) : %d\n"
                       "    Global nodes                       : %D\n"
                       "    Local Elements                     : %D\n"
                       "    Owned nodes                        : %D\n"
                       "    DoF per node                       : %D\n",
                       rp->bp_choice+1, rp->hostname, comm_size,
                       rp->ranks_per_node, vec_type, used_resource,
                       CeedMemTypes[mem_type_backend],
                       P, Q, g_size/rp->num_comp_u, c_end - c_start, l_size/rp->num_comp_u,
                       rp->num_comp_u);
    CHKERRQ(ierr);
  }

  // Create RHS vector
  ierr = VecDuplicate(X_loc, &rhs_loc); CHKERRQ(ierr);
  ierr = VecZeroEntries(rhs_loc); CHKERRQ(ierr);
  ierr = VecGetArrayAndMemType(rhs_loc, &r, &mem_type); CHKERRQ(ierr);
  CeedVectorCreate(ceed, xl_size, &rhs_ceed);
  CeedVectorSetArray(rhs_ceed, MemTypeP2C(mem_type), CEED_USE_POINTER, r);

  ierr = PetscMalloc1(1, &ceed_data); CHKERRQ(ierr);
  ierr = SetupLibceedByDegree(dm, ceed, rp->degree, rp->dim, rp->q_extra,
                              rp->dim, rp->num_comp_u, g_size, xl_size, bp_options[rp->bp_choice],
                              ceed_data, true, rhs_ceed, &target); CHKERRQ(ierr);

  // Gather RHS
  CeedVectorTakeArray(rhs_ceed, MemTypeP2C(mem_type), NULL);
  ierr = VecRestoreArrayAndMemType(rhs_loc, &r); CHKERRQ(ierr);
  ierr = VecZeroEntries(rhs); CHKERRQ(ierr);
  ierr = DMLocalToGlobal(dm, rhs_loc, ADD_VALUES, rhs); CHKERRQ(ierr);
  CeedVectorDestroy(&rhs_ceed);

  // Create the error QFunction
  CeedQFunctionCreateInterior(ceed, 1, bp_options[rp->bp_choice].error,
                              bp_options[rp->bp_choice].error_loc, &qf_error);
  CeedQFunctionAddInput(qf_error, "u", rp->num_comp_u, CEED_EVAL_INTERP);
  CeedQFunctionAddInput(qf_error, "true_soln", rp->num_comp_u, CEED_EVAL_NONE);
  CeedQFunctionAddOutput(qf_error, "error", rp->num_comp_u, CEED_EVAL_NONE);

  // Create the error operator
  CeedOperatorCreate(ceed, qf_error, CEED_QFUNCTION_NONE, CEED_QFUNCTION_NONE,
                     &op_error);
  CeedOperatorSetField(op_error, "u", ceed_data->elem_restr_u,
                       ceed_data->basis_u, CEED_VECTOR_ACTIVE);
  CeedOperatorSetField(op_error, "true_soln", ceed_data->elem_restr_u_i,
                       CEED_BASIS_COLLOCATED, target);
  CeedOperatorSetField(op_error, "error", ceed_data->elem_restr_u_i,
                       CEED_BASIS_COLLOCATED, CEED_VECTOR_ACTIVE);

  // Set up Mat
  user_O->comm = rp->comm;
  user_O->dm = dm;
  user_O->X_loc = X_loc;
  ierr = VecDuplicate(X_loc, &user_O->Y_loc); CHKERRQ(ierr);
  user_O->x_ceed = ceed_data->x_ceed;
  user_O->y_ceed = ceed_data->y_ceed;
  user_O->op = ceed_data->op_apply;
  user_O->ceed = ceed;

  ierr = KSPCreate(rp->comm, &ksp); CHKERRQ(ierr);
  {
    PC pc;
    ierr = KSPGetPC(ksp, &pc); CHKERRQ(ierr);
    if (rp->bp_choice == CEED_BP1 || rp->bp_choice == CEED_BP2) {
      ierr = PCSetType(pc, PCJACOBI); CHKERRQ(ierr);
      ierr = PCJacobiSetType(pc, PC_JACOBI_ROWSUM); CHKERRQ(ierr);
    } else {
      ierr = PCSetType(pc, PCNONE); CHKERRQ(ierr);
    }
    ierr = KSPSetType(ksp, KSPCG); CHKERRQ(ierr);
    ierr = KSPSetNormType(ksp, KSP_NORM_NATURAL); CHKERRQ(ierr);
    ierr = KSPSetTolerances(ksp, 1e-10, PETSC_DEFAULT, PETSC_DEFAULT,
                            PETSC_DEFAULT); CHKERRQ(ierr);
  }
  ierr = KSPSetOperators(ksp, mat_O, mat_O); CHKERRQ(ierr);

  // First run's performance log is not considered for benchmarking purposes
  ierr = KSPSetTolerances(ksp, 1e-10, PETSC_DEFAULT, PETSC_DEFAULT, 1);
  CHKERRQ(ierr);
  my_rt_start = MPI_Wtime();
  ierr = KSPSolve(ksp, rhs, X); CHKERRQ(ierr);
  my_rt = MPI_Wtime() - my_rt_start;
  ierr = MPI_Allreduce(MPI_IN_PLACE, &my_rt, 1, MPI_DOUBLE, MPI_MIN, rp->comm);
  CHKERRQ(ierr);
  // Set maxits based on first iteration timing
  if (my_rt > 0.02) {
    ierr = KSPSetTolerances(ksp, 1e-10, PETSC_DEFAULT, PETSC_DEFAULT,
                            rp->ksp_max_it_clip[0]);
    CHKERRQ(ierr);
  } else {
    ierr = KSPSetTolerances(ksp, 1e-10, PETSC_DEFAULT, PETSC_DEFAULT,
                            rp->ksp_max_it_clip[1]);
    CHKERRQ(ierr);
  }
  ierr = KSPSetFromOptions(ksp); CHKERRQ(ierr);

  // Timed solve
  ierr = VecZeroEntries(X); CHKERRQ(ierr);
  ierr = PetscBarrier((PetscObject)ksp); CHKERRQ(ierr);

  // -- Performance logging
  ierr = PetscLogStagePush(rp->solve_stage); CHKERRQ(ierr);

  // -- Solve
  my_rt_start = MPI_Wtime();
  ierr = KSPSolve(ksp, rhs, X); CHKERRQ(ierr);
  my_rt = MPI_Wtime() - my_rt_start;

  // -- Performance logging
  ierr = PetscLogStagePop();

  // Output results
  {
    KSPType ksp_type;
    KSPConvergedReason reason;
    PetscReal rnorm;
    PetscInt its;
    ierr = KSPGetType(ksp, &ksp_type); CHKERRQ(ierr);
    ierr = KSPGetConvergedReason(ksp, &reason); CHKERRQ(ierr);
    ierr = KSPGetIterationNumber(ksp, &its); CHKERRQ(ierr);
    ierr = KSPGetResidualNorm(ksp, &rnorm); CHKERRQ(ierr);
    if (!rp->test_mode || reason < 0 || rnorm > 1e-8) {
      ierr = PetscPrintf(rp->comm,
                         "  KSP:\n"
                         "    KSP Type                           : %s\n"
                         "    KSP Convergence                    : %s\n"
                         "    Total KSP Iterations               : %D\n"
                         "    Final rnorm                        : %e\n",
                         ksp_type, KSPConvergedReasons[reason], its,
                         (double)rnorm); CHKERRQ(ierr);
    }
    if (!rp->test_mode) {
      ierr = PetscPrintf(rp->comm,"  Performance:\n"); CHKERRQ(ierr);
    }
    {
      PetscReal max_error;
      ierr = ComputeErrorMax(user_O, op_error, X, target, &max_error);
      CHKERRQ(ierr);
      PetscReal tol = 5e-2;
      if (!rp->test_mode || max_error > tol) {
        ierr = MPI_Allreduce(&my_rt, &rt_min, 1, MPI_DOUBLE, MPI_MIN, rp->comm);
        CHKERRQ(ierr);
        ierr = MPI_Allreduce(&my_rt, &rt_max, 1, MPI_DOUBLE, MPI_MAX, rp->comm);
        CHKERRQ(ierr);
        ierr = PetscPrintf(rp->comm,
                           "    Pointwise Error (max)              : %e\n"
                           "    CG Solve Time                      : %g (%g) sec\n",
                           (double)max_error, rt_max, rt_min); CHKERRQ(ierr);
      }
    }
    if (!rp->test_mode) {
      ierr = PetscPrintf(rp->comm,
                         "    DoFs/Sec in CG                     : %g (%g) million\n",
                         1e-6*g_size*its/rt_max,
                         1e-6*g_size*its/rt_min); CHKERRQ(ierr);
    }
  }

  if (rp->write_solution) {
    PetscViewer vtk_viewer_soln;

    ierr = PetscViewerCreate(rp->comm, &vtk_viewer_soln); CHKERRQ(ierr);
    ierr = PetscViewerSetType(vtk_viewer_soln, PETSCVIEWERVTK); CHKERRQ(ierr);
    ierr = PetscViewerFileSetName(vtk_viewer_soln, "solution.vtu"); CHKERRQ(ierr);
    ierr = VecView(X, vtk_viewer_soln); CHKERRQ(ierr);
    ierr = PetscViewerDestroy(&vtk_viewer_soln); CHKERRQ(ierr);
  }

  // Cleanup
  ierr = VecDestroy(&X); CHKERRQ(ierr);
  ierr = VecDestroy(&X_loc); CHKERRQ(ierr);
  ierr = VecDestroy(&user_O->Y_loc); CHKERRQ(ierr);
  ierr = MatDestroy(&mat_O); CHKERRQ(ierr);
  ierr = PetscFree(user_O); CHKERRQ(ierr);
  ierr = CeedDataDestroy(0, ceed_data); CHKERRQ(ierr);

  ierr = VecDestroy(&rhs); CHKERRQ(ierr);
  ierr = VecDestroy(&rhs_loc); CHKERRQ(ierr);
  ierr = KSPDestroy(&ksp); CHKERRQ(ierr);
  CeedVectorDestroy(&target);
  CeedQFunctionDestroy(&qf_error);
  CeedOperatorDestroy(&op_error);
  CeedDestroy(&ceed);
  PetscFunctionReturn(0);
}

static PetscErrorCode Run(RunParams rp, PetscInt num_resources,
                          char *const *ceed_resources, PetscInt num_bp_choices,
                          const BPType *bp_choices) {
  PetscInt ierr;
  DM dm;

  PetscFunctionBeginUser;
  // Setup DM
  if (rp->read_mesh) {
    ierr = DMPlexCreateFromFile(PETSC_COMM_WORLD, rp->filename, PETSC_TRUE, &dm);
    CHKERRQ(ierr);
  } else {
    if (rp->user_l_nodes) {
      // Find a nicely composite number of elements no less than global nodes
      PetscMPIInt size;
      ierr = MPI_Comm_size(rp->comm, &size); CHKERRQ(ierr);
      for (PetscInt g_elem =
             PetscMax(1, size * rp->local_nodes / PetscPowInt(rp->degree, rp->dim));
           ;
           g_elem++) {
        Split3(g_elem, rp->mesh_elem, true);
        if (Max3(rp->mesh_elem) / Min3(rp->mesh_elem) <= 2) break;
      }
    }
    ierr = DMPlexCreateBoxMesh(PETSC_COMM_WORLD, rp->dim, PETSC_FALSE,
                               rp->mesh_elem,
                               NULL, NULL, NULL, PETSC_TRUE, &dm); CHKERRQ(ierr);
  }

  {
    DM dm_dist = NULL;
    PetscPartitioner part;

    ierr = DMPlexGetPartitioner(dm, &part); CHKERRQ(ierr);
    ierr = PetscPartitionerSetFromOptions(part); CHKERRQ(ierr);
    ierr = DMPlexDistribute(dm, 0, NULL, &dm_dist); CHKERRQ(ierr);
    if (dm_dist) {
      ierr = DMDestroy(&dm); CHKERRQ(ierr);
      dm  = dm_dist;
    }
  }
  // Disable default VECSTANDARD *after* distribution (which creates a Vec)
  ierr = DMSetVecType(dm, NULL); CHKERRQ(ierr);

  for (PetscInt b = 0; b < num_bp_choices; b++) {
    DM dm_deg;
    VecType vec_type;
    PetscInt q_extra = rp->q_extra;
    rp->bp_choice = bp_choices[b];
    rp->num_comp_u = bp_options[rp->bp_choice].num_comp_u;
    rp->q_extra = q_extra < 0 ? bp_options[rp->bp_choice].q_extra : q_extra;
    ierr = DMClone(dm, &dm_deg); CHKERRQ(ierr);
    ierr = DMGetVecType(dm, &vec_type); CHKERRQ(ierr);
    ierr = DMSetVecType(dm_deg, vec_type); CHKERRQ(ierr);
    // Create DM
    PetscInt dim;
    ierr = DMGetDimension(dm_deg, &dim); CHKERRQ(ierr);
    ierr = SetupDMByDegree(dm_deg, rp->degree, rp->num_comp_u, dim,
                           bp_options[rp->bp_choice].enforce_bc,
                           bp_options[rp->bp_choice].bc_func); CHKERRQ(ierr);
    for (PetscInt r = 0; r < num_resources; r++) {
      ierr = RunWithDM(rp, dm_deg, ceed_resources[r]); CHKERRQ(ierr);
    }
    ierr = DMDestroy(&dm_deg); CHKERRQ(ierr);
    rp->q_extra = q_extra;
  }

  ierr = DMDestroy(&dm); CHKERRQ(ierr);
  PetscFunctionReturn(0);
}

int main(int argc, char **argv) {
  PetscInt ierr, comm_size;
  RunParams rp;
  MPI_Comm comm;
  char filename[PETSC_MAX_PATH_LEN];
  char *ceed_resources[30];
  PetscInt num_ceed_resources = 30;
  char hostname[PETSC_MAX_PATH_LEN];

  PetscInt dim = 3, mesh_elem[3] = {3, 3, 3};
  PetscInt num_degrees = 30, degree[30] = {}, num_local_nodes = 2,
                                          local_nodes[2] = {};
  PetscMPIInt ranks_per_node;
  PetscBool degree_set;
  BPType bp_choices[10];
  PetscInt num_bp_choices = 10;

  // Initialize PETSc
  ierr = PetscInitialize(&argc, &argv, NULL, help);
  if (ierr) return ierr;
  comm = PETSC_COMM_WORLD;
  ierr = MPI_Comm_size(comm, &comm_size);
  if (ierr != MPI_SUCCESS) return ierr;
  #if defined(PETSC_HAVE_MPI_PROCESS_SHARED_MEMORY)
  {
    MPI_Comm splitcomm;
    ierr = MPI_Comm_split_type(comm, MPI_COMM_TYPE_SHARED, 0, MPI_INFO_NULL,
                               &splitcomm);
    CHKERRQ(ierr);
    ierr = MPI_Comm_size(splitcomm, &ranks_per_node); CHKERRQ(ierr);
    ierr = MPI_Comm_free(&splitcomm); CHKERRQ(ierr);
  }
  #else
  ranks_per_node = -1; // Unknown
  #endif

  // Setup all parameters needed in Run()
  ierr = PetscMalloc1(1, &rp); CHKERRQ(ierr);
  rp->comm = comm;

  // Read command line options
  ierr = PetscOptionsBegin(comm, NULL, "CEED BPs in PETSc", NULL);
  CHKERRQ(ierr);
  {
    PetscBool set;
    ierr = PetscOptionsEnumArray("-problem", "CEED benchmark problem to solve",
                                 NULL,
                                 bp_types, (PetscEnum *)bp_choices, &num_bp_choices, &set);
    CHKERRQ(ierr);
    if (!set) {
      bp_choices[0] = CEED_BP1;
      num_bp_choices = 1;
    }
  }
  rp->test_mode = PETSC_FALSE;
  ierr = PetscOptionsBool("-test",
                          "Testing mode (do not print unless error is large)",
                          NULL, rp->test_mode, &rp->test_mode, NULL); CHKERRQ(ierr);
  rp->write_solution = PETSC_FALSE;
  ierr = PetscOptionsBool("-write_solution", "Write solution for visualization",
                          NULL, rp->write_solution, &rp->write_solution, NULL);
  CHKERRQ(ierr);
  degree[0] = rp->test_mode ? 3 : 2;
  ierr = PetscOptionsIntArray("-degree",
                              "Polynomial degree of tensor product basis", NULL,
                              degree, &num_degrees, &degree_set); CHKERRQ(ierr);
  if (!degree_set)
    num_degrees = 1;
  rp->q_extra = PETSC_DECIDE;
  ierr = PetscOptionsInt("-q_extra",
                         "Number of extra quadrature points (-1 for auto)", NULL,
                         rp->q_extra, &rp->q_extra, NULL); CHKERRQ(ierr);
  {
    PetscBool set;
    ierr = PetscOptionsStringArray("-ceed",
                                   "CEED resource specifier (comma-separated list)", NULL,
                                   ceed_resources, &num_ceed_resources, &set); CHKERRQ(ierr);
    if (!set) {
      ierr = PetscStrallocpy( "/cpu/self", &ceed_resources[0]); CHKERRQ(ierr);
      num_ceed_resources = 1;
    }
  }
  ierr = PetscGetHostName(hostname, sizeof hostname); CHKERRQ(ierr);
  ierr = PetscOptionsString("-hostname", "Hostname for output", NULL, hostname,
                            hostname, sizeof(hostname), NULL); CHKERRQ(ierr);
  rp->read_mesh = PETSC_FALSE;
  ierr = PetscOptionsString("-mesh", "Read mesh from file", NULL, filename,
                            filename, sizeof(filename), &rp->read_mesh);
  CHKERRQ(ierr);
  rp->filename = filename;
  if (!rp->read_mesh) {
    PetscInt tmp = dim;
    ierr = PetscOptionsIntArray("-cells", "Number of cells per dimension", NULL,
                                mesh_elem, &tmp, NULL); CHKERRQ(ierr);
  }
  local_nodes[0] = 1000;
  ierr = PetscOptionsIntArray("-local_nodes",
                              "Target number of locally owned nodes per "
                              "process (single value or min,max)",
                              NULL, local_nodes, &num_local_nodes, &rp->user_l_nodes);
  CHKERRQ(ierr);
  if (num_local_nodes < 2)
    local_nodes[1] = 2 * local_nodes[0];
  {
    PetscInt two = 2;
    rp->ksp_max_it_clip[0] = 5;
    rp->ksp_max_it_clip[1] = 20;
    ierr = PetscOptionsIntArray("-ksp_max_it_clip",
                                "Min and max number of iterations to use during benchmarking",
                                NULL, rp->ksp_max_it_clip, &two, NULL); CHKERRQ(ierr);
  }
  if (!degree_set) {
    PetscInt max_degree = 8;
    ierr = PetscOptionsInt("-max_degree",
                           "Range of degrees [1, max_degree] to run with",
                           NULL, max_degree, &max_degree, NULL);
    CHKERRQ(ierr);
    for (PetscInt i = 0; i < max_degree; i++)
      degree[i] = i + 1;
    num_degrees = max_degree;
  }
  {
    PetscBool flg;
    PetscInt p = ranks_per_node;
    ierr = PetscOptionsInt("-p", "Number of MPI ranks per node", NULL,
                           p, &p, &flg);
    CHKERRQ(ierr);
    if (flg) ranks_per_node = p;
  }

  ierr = PetscOptionsEnd();
  CHKERRQ(ierr);

  // Register PETSc logging stage
  ierr = PetscLogStageRegister("Solve Stage", &rp->solve_stage);
  CHKERRQ(ierr);

  rp->hostname = hostname;
  rp->dim = dim;
  rp->mesh_elem = mesh_elem;
  rp->ranks_per_node = ranks_per_node;

  for (PetscInt d = 0; d < num_degrees; d++) {
    PetscInt deg = degree[d];
    for (PetscInt n = local_nodes[0]; n < local_nodes[1]; n *= 2) {
      rp->degree = deg;
      rp->local_nodes = n;
      ierr = Run(rp, num_ceed_resources, ceed_resources,
                 num_bp_choices, bp_choices); CHKERRQ(ierr);
    }
  }
  // Clear memory
  ierr = PetscFree(rp); CHKERRQ(ierr);
  for (PetscInt i=0; i<num_ceed_resources; i++) {
    ierr = PetscFree(ceed_resources[i]); CHKERRQ(ierr);
  }
  return PetscFinalize();
}


/*
 Copyright (C) 2002 M. Marques, A. Castro, A. Rubio, G. Bertsch, D. Strubbe

 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, 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.

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <math.h>
#include <gsl/gsl_complex_math.h>

#include "symbols.h"
#include "gsl_userdef.h"

/* The symbol table: a chain of `struct symrec'.  */
symrec *sym_table = (symrec *)0;

void str_tolower(char *in)
{
  for(; *in; in++)
    *in = (char)tolower(*in);
}

void sym_mark_table_used ()
{
  symrec *ptr;

  for (ptr = sym_table; ptr != (symrec *) 0;
       ptr = (symrec *)ptr->next)
  {
    ptr->used = 1;
  }
}

symrec *putsym (const char *sym_name, symrec_type sym_type)
{
  symrec *ptr;
  ptr = (symrec *)malloc(sizeof(symrec));

  /* names are always lowercase */
  ptr->name = strdup(sym_name);
  str_tolower(ptr->name);

  ptr->def  = 0;
  ptr->used = 0;
  ptr->type = sym_type;
  GSL_SET_COMPLEX(&ptr->value.c, 0, 0); /* set value to 0 even if fctn.  */
  ptr->next = (struct symrec *)sym_table;
  sym_table = ptr;
  return ptr;
}

symrec *getsym (const char *sym_name)
{
  symrec *ptr;
  for (ptr = sym_table; ptr != (symrec *) 0;
       ptr = (symrec *)ptr->next)
    if (strcasecmp(ptr->name,sym_name) == 0){
      ptr->used = 1;
      return ptr;
    }
  return (symrec *) 0;
}

int rmsym (const char *sym_name)
{
  symrec *ptr, *prev;
  for (prev = (symrec *) 0, ptr = sym_table; ptr != (symrec *) 0;
       prev = ptr, ptr = ptr->next)
    if (strcasecmp(ptr->name,sym_name) == 0){
      if(prev == (symrec *) 0)
	sym_table = ptr->next;
      else
	prev->next = ptr->next;
      free(ptr->name);
      free(ptr);

      return 1;
    }

  return 0;
}

struct init_fntc{
  char *fname;
  int  nargs;
  gsl_complex (*fnctptr)();
};

void sym_notdef (symrec *sym)
{
  fprintf(stderr, "Parser error: symbol '%s' used before being defined.\n", sym->name);
  exit(1);
}

void sym_redef (symrec *sym)
{
  fprintf(stderr, "Parser warning: redefining symbol, previous value ");
  sym_print(stderr, sym);
  fprintf(stderr, "\n");
}

void sym_wrong_arg (symrec *sym)
{
  if(sym->type == S_BLOCK) {
    fprintf(stderr, "Parser error: block name '%s' used in variable context.\n", sym->name);
  } else if(sym->type == S_STR) {
    fprintf(stderr, "Parser error: string variable '%s' used in expression context.\n", sym->name);
  } else {
    fprintf(stderr, "Parser error: function '%s' requires %d argument(s).\n", sym->name, sym->nargs);
  }
  exit(1);
}

static struct init_fntc arith_fncts[] = {
  {"sqrt",   1, (gsl_complex (*)()) &gsl_complex_sqrt},
  {"exp",    1, (gsl_complex (*)()) &gsl_complex_exp},
  {"ln",     1, (gsl_complex (*)()) &gsl_complex_log},
  {"log",    1, (gsl_complex (*)()) &gsl_complex_log},
  {"log10",  1, (gsl_complex (*)()) &gsl_complex_log10},
  {"logb",   2, (gsl_complex (*)()) &gsl_complex_log_b}, /* takes two arguments logb(z, b) = log_b(z) */

  {"arg",    1, (gsl_complex (*)()) &gsl_complex_carg},
  {"abs",    1, (gsl_complex (*)()) &gsl_complex_cabs},
  {"abs2",   1, (gsl_complex (*)()) &gsl_complex_cabs2},
  {"logabs", 1, (gsl_complex (*)()) &gsl_complex_clogabs},

  {"conjg",  1, (gsl_complex (*)()) &gsl_complex_conjugate},
  {"inv",    1, (gsl_complex (*)()) &gsl_complex_inverse},

  {"sin",    1, (gsl_complex (*)()) &gsl_complex_sin},
  {"cos",    1, (gsl_complex (*)()) &gsl_complex_cos},
  {"tan",    1, (gsl_complex (*)()) &gsl_complex_tan},
  {"sec",    1, (gsl_complex (*)()) &gsl_complex_sec},
  {"csc",    1, (gsl_complex (*)()) &gsl_complex_csc},
  {"cot",    1, (gsl_complex (*)()) &gsl_complex_cot},

  {"asin",   1, (gsl_complex (*)()) &gsl_complex_arcsin},
  {"acos",   1, (gsl_complex (*)()) &gsl_complex_arccos},
  {"atan",   1, (gsl_complex (*)()) &gsl_complex_arctan},
  {"atan2",  2, (gsl_complex (*)()) &gsl_complex_arctan2}, /* takes two arguments atan2(y,x) = atan(y/x) */
  {"asec",   1, (gsl_complex (*)()) &gsl_complex_arcsec},
  {"acsc",   1, (gsl_complex (*)()) &gsl_complex_arccsc},
  {"acot",   1, (gsl_complex (*)()) &gsl_complex_arccot},

  {"sinh",   1, (gsl_complex (*)()) &gsl_complex_sinh},
  {"cosh",   1, (gsl_complex (*)()) &gsl_complex_cosh},
  {"tanh",   1, (gsl_complex (*)()) &gsl_complex_tanh},
  {"sech",   1, (gsl_complex (*)()) &gsl_complex_sech},
  {"csch",   1, (gsl_complex (*)()) &gsl_complex_csch},
  {"coth",   1, (gsl_complex (*)()) &gsl_complex_coth},

  {"asinh",  1, (gsl_complex (*)()) &gsl_complex_arcsinh},
  {"acosh",  1, (gsl_complex (*)()) &gsl_complex_arccosh},
  {"atanh",  1, (gsl_complex (*)()) &gsl_complex_arctanh},
  {"asech",  1, (gsl_complex (*)()) &gsl_complex_arcsech},
  {"acsch",  1, (gsl_complex (*)()) &gsl_complex_arccsch},
  {"acoth",  1, (gsl_complex (*)()) &gsl_complex_arccoth},

/* user-defined step function. this is not available in GSL,
   but we use GSL namespacing and macros here. */
  {"step",   1, (gsl_complex (*)()) &gsl_complex_step_real},

/* Minimum and maximum of two arguments (comparing real parts) */
  {"min",    2, (gsl_complex (*)()) &gsl_complex_min_real},
  {"max",    2, (gsl_complex (*)()) &gsl_complex_max_real},

  {"erf",    1, (gsl_complex (*)()) &gsl_complex_erf},

  {"realpart", 1, (gsl_complex (*)()) &gsl_complex_realpart},
  {"imagpart", 1, (gsl_complex (*)()) &gsl_complex_imagpart},
  {"round",   1, (gsl_complex (*)()) &gsl_complex_round},
  {"floor",   1, (gsl_complex (*)()) &gsl_complex_floor},
  {"ceiling", 1, (gsl_complex (*)()) &gsl_complex_ceiling},

  {"rand",    0, (gsl_complex (*)()) &gsl_complex_rand},

  {0, 0, 0}
};

struct init_cnst{
	char *fname;
	double re;
	double im;
};

static struct init_cnst arith_cnts[] = {
	{"pi",    M_PI, 0},
	{"e",      M_E, 0},
	{"i",        0, 1},
	{"true",     1, 0},
	{"yes",      1, 0},
	{"false",    0, 0},
	{"no",       0, 0},
	{0,          0, 0}
};

char *reserved_symbols[] = {
  "x", "y", "z", "r", "w", "t", 0
};

void sym_init_table ()  /* puts arithmetic functions in table. */
{
  int i;
  symrec *ptr;
  for (i = 0; arith_fncts[i].fname != 0; i++){
    ptr = putsym (arith_fncts[i].fname, S_FNCT);
    ptr->def = 1;
    ptr->used = 1;
    ptr->nargs = arith_fncts[i].nargs;
    ptr->value.fnctptr = arith_fncts[i].fnctptr;
  }

  /* now the constants */
  for (i = 0; arith_cnts[i].fname != 0; i++){
    ptr = putsym(arith_cnts[i].fname, S_CMPLX);
    ptr->def = 1;
    ptr->used = 1;
    GSL_SET_COMPLEX(&ptr->value.c, arith_cnts[i].re, arith_cnts[i].im);
  }
}

void sym_end_table()
{
  symrec *ptr, *ptr2;

  for (ptr = sym_table; ptr != NULL;){
    free(ptr->name);
    switch(ptr->type){
    case S_STR:
      free(ptr->value.str);
      break;
    case S_BLOCK:
      if(ptr->value.block->n > 0){
	free(ptr->value.block->lines);
      }
      free(ptr->value.block);
      break;
    case S_CMPLX:
    case S_FNCT:
      break;
    }
    ptr2 = ptr->next;
    free(ptr);
    ptr = ptr2;
  }

  sym_table = NULL;
}

/* this function is defined in src/basic/varinfo_low.c */
int varinfo_variable_exists(const char * var_name);

void sym_output_table(int only_unused, int mpiv_node)
{
  FILE *f;
  symrec *ptr;
  int any_unused = 0;

  if(mpiv_node != 0) {
    return;
  }

  if(only_unused) {
    f = stderr;
  } else {
    f = stdout;
  }

  for(ptr = sym_table; ptr != NULL; ptr = ptr->next){
    if(only_unused && ptr->used == 1) continue;
    if(only_unused && varinfo_variable_exists(ptr->name)) continue;
    if(any_unused == 0) {
      fprintf(f, "\nParser warning: possible mistakes in input file.\n");
      fprintf(f, "List of variable assignments not used by parser:\n");
      any_unused = 1;
    }

    sym_print(f, ptr);
  }
  if(any_unused == 1) {
    fprintf(f, "\n");
  }
}

void sym_print(FILE *f, const symrec *ptr)
{
  fprintf(f, "%s", ptr->name);
  switch(ptr->type){
  case S_CMPLX:
    if(fabs(GSL_IMAG(ptr->value.c)) < 1.0e-14){
      fprintf(f, " = %f\n", GSL_REAL(ptr->value.c));
    } else {
      fprintf(f, " = (%f,%f)\n", GSL_REAL(ptr->value.c), GSL_IMAG(ptr->value.c));
    }
    break;
  case S_STR:
    fprintf(f, " = \"%s\"\n", ptr->value.str);
    break;
  case S_BLOCK:
    fprintf(f, "%s\n", " <= BLOCK");
    break;
  case S_FNCT:
    fprintf(f, "%s\n", " <= FUNCTION");
    break;
  }
}


/*
 * GSLIntegrator.h
 *
 *  Created on: 6 cze 2020
 *      Author: tomhof
 */

#ifndef SRC_GSLINTEGRATOR_H_
#define SRC_GSLINTEGRATOR_H_

#include <stdlib.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_monte.h>
#include <gsl/gsl_monte_plain.h>
#include <gsl/gsl_monte_miser.h>
#include <gsl/gsl_monte_vegas.h>

class GSLIntegrator {
private:
	size_t calls = 50000;
public:
	GSLIntegrator();
	virtual ~GSLIntegrator();
    double integrate(double (*f)(double * x_array, size_t dim, void * params), double a1, double b1, double a2, double b2, double* int_err);
    void display_results(char *title, double result, double error);

	size_t getCalls() const {
		return calls;
	}

	void setCalls(size_t calls = 50000) {
		this->calls = calls;
	}
};

#endif /* SRC_GSLINTEGRATOR_H_ */


\documentclass{report}
\usepackage[T1]{fontenc}
\usepackage{bera}

\usepackage[pdftex,usenames,dvipsnames]{color}
\usepackage{listings}
\definecolor{mycode}{rgb}{0.9,0.9,1}
\lstset{language=c++,tabsize=1,basicstyle=\scriptsize,backgroundcolor=\color{mycode}}

\usepackage{hyperref}
\usepackage{fancyhdr}
\pagestyle{fancy}
\usepackage{verbatim}
\begin{document}

%\title{The Powell Class for Minimization of Functions}
%\author{G.A.}
%\maketitle

\begin{comment}
@o Minimizer.h -t
@{
/*
@i license.txt
*/
@}
\end{comment}

\section{A class to minimize a function without using Derivatives}
This requires the GNU Scientific Library, libraries and devel. headers.

Explain usage here. FIXME.

This is the structure of this file:

@o Minimizer.h -t
@{
#ifndef MINIMIZER_H
#define MINIMIZER_H

#include <iostream>
#include <vector>
#include <stdexcept>
extern "C" {
#include <gsl/gsl_multimin.h>
}

namespace PsimagLite {
	@<MockVector@>
	@<MyFunction@>
	@<theClassHere@>
} // namespace PsimagLite
#endif // MINIMIZER_H

@}

And this is the class here:

@d theClassHere
@{
template<typename RealType,typename FunctionType>
class Minimizer {
	@<privateTypedefsAndConstants@>
public:
	@<constructor@>
	@<destructor@>
	@<publicFunctions@>
private:
	@<privateFunctions@>
	@<privateData@>
}; // class Minimizer
@}

@d privateTypedefsAndConstants
@{
typedef typename FunctionType::FieldType FieldType;
typedef typename Vector<FieldType>::Type VectorType;
typedef Minimizer<RealType,FunctionType> ThisType;

@}

@d privateData
@{
FunctionType& function_;
SizeType maxIter_;
const gsl_multimin_fminimizer_type *gslT_;
gsl_multimin_fminimizer *gslS_;
@}

@d constructor
@{
Minimizer(FunctionType& function,SizeType maxIter)
		: function_(function),
		  maxIter_(maxIter),
		  gslT_(gsl_multimin_fminimizer_nmsimplex2),
		  gslS_(gsl_multimin_fminimizer_alloc(gslT_,function_.size()))
{
}
@}

@d publicFunctions
@{
@<simplex@>
@}

@d destructor
@{
~Minimizer()
{
	gsl_multimin_fminimizer_free (gslS_);
}
@}

@d simplex
@{
int simplex(VectorType& minVector,RealType delta=1e-3,RealType tolerance=1e-3)
{
	gsl_vector *x;
	/* Starting point,  */
	x = gsl_vector_alloc (function_.size());
	for (SizeType i=0;i<minVector.size();i++)
		gsl_vector_set (x, i, minVector[i]);

	gsl_vector *xs;
	xs = gsl_vector_alloc (function_.size());
	for (SizeType i=0;i<minVector.size();i++)
		gsl_vector_set (xs, i, delta);

	gsl_multimin_function func;
	func.f= myFunction<FunctionType>;
	func.n = function_.size();
	func.params = &function_;
	gsl_multimin_fminimizer_set (gslS_, &func, x, xs);

	for (SizeType iter=0;iter<maxIter_;iter++) {
		int status = gsl_multimin_fminimizer_iterate (gslS_);

		if (status) throw RuntimeError("Minimizer::simplex(...): Error encountered\n");

		RealType size = gsl_multimin_fminimizer_size(gslS_);
		status = gsl_multimin_test_size(size, tolerance);

		if (status == GSL_SUCCESS) {
			found(minVector,gslS_->x,iter);
			gsl_vector_free (x);
			gsl_vector_free (xs);
			return iter;
		}
	}
	gsl_vector_free (x);
	gsl_vector_free (xs);
	return -1;
}
@}

@d privateFunctions
@{
@<found@>
@}

@d found
@{
void found(VectorType& minVector,gsl_vector* x,SizeType iter)
{
	for (SizeType i=0;i<minVector.size();i++)
		minVector[i] = gsl_vector_get(x,i);
}
@}

@d MockVector
@{
template<typename FieldType>
class MockVector {
public:
	MockVector(const gsl_vector *v) : v_(v)
	{
	}
	const FieldType& operator[](SizeType i) const
	{
		return v_->data[i];
	}
	SizeType size() const { return v_->size; }
private:
	const gsl_vector *v_;
}; // class MockVector
@}

@d MyFunction
@{
template<typename FunctionType>
typename FunctionType::FieldType myFunction(const gsl_vector *v, void *params)
{
	MockVector<typename FunctionType::FieldType> mv(v);
	FunctionType* ft = (FunctionType *)params;
	return ft->operator()(mv);
}
@}

\end{document}


/* Siconos-Numerics, Copyright INRIA 2005-2018.
 * Siconos is a program dedicated to modeling, simulation and control
 * of non smooth dynamical systems.
 * Siconos is a free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * Siconos 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Siconos; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Contact: Vincent ACARY, siconos-team@lists.gforge.inria.fr
 */

#ifndef SiconosLAPACKE_H
#define SiconosLAPACKE_H

#include "SiconosBlas.h"
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>

// -------- Headers and routines naming conventions for the different Lapack implementations --------

// --- Intel MKL Header ---
#if defined(HAS_MKL_LAPACKE)
#include <mkl_lapacke.h>
#else
// Standard lapacke header
#include <lapacke.h>
#endif

// Name of the routines
#define LAPACK_NAME(N) LAPACKE_##N

#define LA_TRANS 'T'
#define LA_NOTRANS 'N'
#define LA_UP 'U'
#define LA_LO 'L'
#define LA_NONUNIT 'N'
#define LA_UNIT 'U'

#define INTEGER(X) X
#define INTEGERP(X) X
#define CHAR(X) X

#ifndef lapack_int
#define lapack_int int
#endif

// --- DGESVD ---
#if defined(HAS_LAPACK_DGESVD)
#define WRAP_DGESVD(F,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,INFO)      \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12)
#else
#define WRAP_DGESVD(F,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,INFO)      \
  fprintf(stderr, "Your lapack version misses dgesvd function.\n");
#endif

// --- DGETRS ---
#define WRAP_DGETRS(F,A1,A2,A3,A4,A5,A6,A7,A8,INFO)   \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7,A8)

// --- DGESV ---
#define WRAP_DGESV(F,A1,A2,A3,A4,A5,A6,A7,INFO)   \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7)

// --- DGELS ---
#if defined(HAS_LAPACK_DGELS)
#define WRAP_DGELS(F,A1,A2,A3,A4,A5,A6,A7,A8,INFO)    \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7,A8)
#else
#define WRAP_DGELS(F,A1,A2,A3,A4,A5,A6,A7,A8,INFO)                      \
  fprintf(stderr, "Your lapack version misses dgels function.\n");
#endif

// --- DGETRI ---
#define WRAP_DGETRI(F,A1,A2,A3,A4,INFO)         \
  INFO = F(CblasColMajor,A1,A2,A3,A4)

// --- DPOTRF ---
#define WRAP_DPOTRF(F,A1,A2,A3,A4,INFO)  \
  INFO = F(CblasColMajor,A1,A2,A3,A4)

// --- DGETRF ---
#define WRAP_DGETRF(F,A1,A2,A3,A4,A5,INFO)  \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5)

// --- DTRTRS ---
#if defined(HAS_LAPACK_DTRTRS)
#define WRAP_DTRTRS(F,A1,A2,A3,A4,A5,A6,A7,A8,A9,INFO)  \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7,A8,A9)
#else
#define WRAP_DTRTRS(F,A1,A2,A3,A4,A5,A6,A7,A8,A9,INFO)                \
  fprintf(stderr, "Your lapack version misses dtrtrs function.\n");
#endif

#endif // SICONOSLAPACKE_H


//====---- Sudoku/Board_Section_iterator.h                            ----====//
//
// Location aware iterator implementation for Sections
//====--------------------------------------------------------------------====//
//
// The iterator can return a Location object.
// Included by Board_Section.h
//
//====--------------------------------------------------------------------====//
#pragma once

#include "Sudoku/Location.h"
#include "Sudoku/Location_Utilities.h"
#include "Sudoku/traits.h"
#include <gsl/gsl> // index

#include <iterator>
#include <type_traits>

#include "Board.fwd.h" // Forward declarations

#include <cassert>

namespace Sudoku::Board_Section
{
template<typename T, int N, Section S, bool is_const, bool is_reverse>
class Section_iterator;

//====--- Aliases --------------------------------------------------------====//
template<typename T, int N>
using Row_iterator = Section_iterator<T, N, Section::row, false, false>;
template<typename T, int N>
using Col_iterator = Section_iterator<T, N, Section::col, false, false>;
template<typename T, int N>
using Block_iterator = Section_iterator<T, N, Section::block, false, false>;
template<typename T, int N>
using const_Row_iterator = Section_iterator<T, N, Section::row, true, false>;
template<typename T, int N>
using const_Col_iterator = Section_iterator<T, N, Section::col, true, false>;
template<typename T, int N>
using const_Block_iterator =
	Section_iterator<T, N, Section::block, true, false>;
template<typename T, int N>
using reverse_Row_iterator = Section_iterator<T, N, Section::row, false, true>;
template<typename T, int N>
using reverse_Col_iterator = Section_iterator<T, N, Section::col, false, true>;
template<typename T, int N>
using reverse_Block_iterator =
	Section_iterator<T, N, Section::block, false, true>;
template<typename T, int N>
using const_reverse_Row_iterator =
	Section_iterator<T, N, Section::row, true, true>;
template<typename T, int N>
using const_reverse_Col_iterator =
	Section_iterator<T, N, Section::col, true, true>;
template<typename T, int N>
using const_reverse_Block_iterator =
	Section_iterator<T, N, Section::block, true, true>;

//====--------------------------------------------------------------------====//
template<typename T, int N, Section S, bool is_const, bool is_reverse>
class Section_iterator
{
	using owner_type =
		std::conditional_t<is_const, Board<T, N> const, Board<T, N>>;
	using Location       = ::Sudoku::Location<N>;
	using Location_Block = ::Sudoku::Location_Block<N>;

public:
	// member types
	using difference_type   = std::ptrdiff_t;
	using value_type        = T;
	using pointer           = std::conditional_t<is_const, T const*, T*>;
	using reference         = std::conditional_t<is_const, T const&, T&>;
	using iterator_category = std::random_access_iterator_tag;

	// Constructors
	constexpr Section_iterator() noexcept
	{ // defaults to [r]begin()
		if constexpr (is_reverse)
			elem_ = elem_size<N> - 1;
	}
	template<
		typename idT,
		typename elT,
		typename = std::enable_if_t<is_int_v<idT> && is_int_v<elT>>>
	constexpr Section_iterator(owner_type* owner, idT id, elT elem) noexcept
		: board_(owner), id_(id), elem_(elem)
	{
		assert(is_valid_size<N>(id));
	}
	// [[implicit]] Type Conversion to const_(reverse_)iterator
	// NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
	constexpr operator Section_iterator<T, N, S, true, is_reverse>()
		const noexcept // (guide format)
	{
		return Section_iterator<T, N, S, true, is_reverse>(board_, id_, elem_);
	}

	//====----------------------------------------------------------------====//
	[[nodiscard]] constexpr Location location() const noexcept
	{
		assert(is_valid_size<N>(elem_));
		using ::Sudoku::Board_Section::Section;
		switch (S)
		{
		case Section::row: return Location(id_, elem_); break;
		case Section::col: return Location(elem_, id_); break;
		case Section::block: return Location_Block(id_, elem_); break;
		}
	}
	// Allows for conversion to Location
	explicit constexpr operator Location() const noexcept { return location(); }

	//====----------------------------------------------------------------====//
	[[nodiscard]] constexpr reference operator*() const noexcept
	{ // Only valid in a dereferenceable location:
		assert(board_ != nullptr && is_valid_size<N>(elem_));
		return board_->operator[](location());
	}
	[[nodiscard]] constexpr pointer operator->() const noexcept
	{ // member access; equivalent to (*p).member
		return std::pointer_traits<pointer>::pointer_to(**this);
	}

	//====----------------------------------------------------------------====//
	constexpr Section_iterator& operator++() noexcept;
	constexpr Section_iterator operator++(int) noexcept;
	constexpr Section_iterator& operator--() noexcept;
	constexpr Section_iterator operator--(int) noexcept;
	constexpr Section_iterator&
		operator+=(const difference_type offset) noexcept;
	constexpr Section_iterator&
		operator-=(const difference_type offset) noexcept;

	[[nodiscard]] friend constexpr difference_type operator-(
		Section_iterator const& left, Section_iterator const& right) noexcept
	{ // difference
		assert(is_same_Section(left, right));
		if constexpr (is_reverse)
			return right.elem_ - left.elem_;
		else
			return left.elem_ - right.elem_;
	}

	[[nodiscard]] constexpr reference
		operator[](const difference_type offset) const noexcept
	{
		return (*(*this + offset));
	}

	//====----------------------------------------------------------------====//
	[[nodiscard]] friend constexpr bool operator==(
		Section_iterator const& left, Section_iterator const& right) noexcept
	{
		assert(is_same_Section(left, right));
		return is_same_Section(left, right) && left.elem_ == right.elem_;
	}
	[[nodiscard]] friend constexpr bool operator<(
		Section_iterator const& left, Section_iterator const& right) noexcept
	{
		assert(is_same_Section(left, right));
		if constexpr (is_reverse)
			return left.elem_ > right.elem_;
		else
			return left.elem_ < right.elem_;
	}

private:
	owner_type* board_{nullptr};
	gsl::index id_{0};
	gsl::index elem_{0};

	friend constexpr bool is_same_Section(
		Section_iterator const& A, Section_iterator const& B) noexcept
	{ // compare address of:
		return A.board_ == B.board_ && A.id_ == B.id_;
	}
};

//====--------------------------------------------------------------------====//
template<typename T, int N, Section S, bool C, bool is_reverse>
constexpr Section_iterator<T, N, S, C, is_reverse>&
	Section_iterator<T, N, S, C, is_reverse>::operator++() noexcept
{ // pre-increment
	assert(board_ != nullptr);
	if constexpr (is_reverse)
	{
		assert(elem_ >= 0);
		--elem_;
	}
	else
	{
		assert(elem_ < elem_size<N>);
		++elem_;
	}
	return (*this);
}
template<typename T, int N, Section S, bool C, bool R>
constexpr Section_iterator<T, N, S, C, R> // NOLINTNEXTLINE(readability/casting)
	Section_iterator<T, N, S, C, R>::operator++(int) noexcept
{ // post-increment
	const Section_iterator pre{*this};
	operator++();
	return pre;
}
template<typename T, int N, Section S, bool C, bool is_reverse>
constexpr Section_iterator<T, N, S, C, is_reverse>&
	Section_iterator<T, N, S, C, is_reverse>::operator--() noexcept
{ // pre-decrement
	assert(board_ != nullptr);
	if constexpr (is_reverse)
	{
		assert(elem_ < elem_size<N> - 1);
		++elem_;
	}
	else
	{
		assert(elem_ > 0);
		--elem_;
	}
	return (*this);
}
template<typename T, int N, Section S, bool C, bool R>
constexpr Section_iterator<T, N, S, C, R> // NOLINTNEXTLINE(readability/casting)
	Section_iterator<T, N, S, C, R>::operator--(int) noexcept
{ // post-decrement
	const Section_iterator pre{*this};
	operator--();
	return pre;
}
template<typename T, int N, Section S, bool C, bool is_reverse>
constexpr Section_iterator<T, N, S, C, is_reverse>&
	Section_iterator<T, N, S, C, is_reverse>::operator+=(
		const difference_type offset) noexcept
{
	assert(offset == 0 || board_ != nullptr);
	if constexpr (is_reverse)
	{
		elem_ -= offset;
		assert(elem_ >= -1);
		assert(elem_ < elem_size<N>);
	}
	else
	{
		elem_ += offset;
		assert(elem_ >= 0);
		assert(elem_ <= elem_size<N>);
	}
	return (*this);
}
template<typename T, int N, Section S, bool C, bool is_reverse>
constexpr Section_iterator<T, N, S, C, is_reverse>&
	Section_iterator<T, N, S, C, is_reverse>::operator-=(
		const difference_type offset) noexcept
{
	return operator+=(-offset);
}

//====--------------------------------------------------------------------====//
// Free functions (non-friend)
//====--------------------------------------------------------------------====//
template<typename T, int N, Section S, bool is_const, bool is_reverse>
[[nodiscard]] constexpr auto operator+(
	Section_iterator<T, N, S, is_const, is_reverse> left,
	typename Section_iterator<T, N, S, is_const, is_reverse>::
		difference_type const offset) noexcept
{ // itr + offset
	return (left += offset);
}
template<typename T, int N, Section S, bool is_const, bool is_reverse>
[[nodiscard]] inline constexpr auto operator+(
	typename Section_iterator<T, N, S, is_const, is_reverse>::
		difference_type const offset,
	Section_iterator<T, N, S, is_const, is_reverse> itr) noexcept
{ // offset + itr
	return (itr += offset);
}
template<typename T, int N, Section S, bool is_const, bool is_reverse>
[[nodiscard]] constexpr auto operator-(
	Section_iterator<T, N, S, is_const, is_reverse> left,
	typename Section_iterator<T, N, S, is_const, is_reverse>::
		difference_type const offset) noexcept
{ // itr - offset
	return (left += -offset);
}

template<typename T, int N, Section S, bool is_const, bool is_reverse>
[[nodiscard]] inline constexpr bool operator!=(
	Section_iterator<T, N, S, is_const, is_reverse> const& left,
	Section_iterator<T, N, S, is_const, is_reverse> const& right) noexcept
{
	return !(left == right);
}
template<typename T, int N, Section S, bool is_const, bool is_reverse>
[[nodiscard]] inline constexpr bool operator>(
	Section_iterator<T, N, S, is_const, is_reverse> const& left,
	Section_iterator<T, N, S, is_const, is_reverse> const& right) noexcept
{
	return (right < left);
}
template<typename T, int N, Section S, bool is_const, bool is_reverse>
[[nodiscard]] inline constexpr bool operator<=(
	Section_iterator<T, N, S, is_const, is_reverse> const& left,
	Section_iterator<T, N, S, is_const, is_reverse> const& right) noexcept
{
	return !(right < left);
}
template<typename T, int N, Section S, bool is_const, bool is_reverse>
[[nodiscard]] inline constexpr bool operator>=(
	Section_iterator<T, N, S, is_const, is_reverse> const& left,
	Section_iterator<T, N, S, is_const, is_reverse> const& right) noexcept
{
	return !(left < right);
}

} // namespace Sudoku::Board_Section


/*
 *  Copyright 2008-2009 NVIDIA Corporation
 *
 *  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.
 */

/*! \file defs.h
 *  \brief Lapack utility definitions for interface routines
 */

#pragma once

#include <lapacke.h>

namespace cusp
{
namespace lapack
{

struct lapack_format {};

struct upper   : public lapack_format {};
struct lower   : public lapack_format {};
struct unit    : public lapack_format {};
struct nonunit : public lapack_format {};
struct evals   : public lapack_format {};
struct evecs   : public lapack_format {};
struct gen_op1 : public lapack_format {};
struct gen_op2 : public lapack_format {};
struct gen_op3 : public lapack_format {};

template< typename LayoutFormat >
struct Orientation {static const lapack_int type;};
template<>
const lapack_int Orientation<cusp::row_major>::type    = LAPACK_ROW_MAJOR;
template<>
const lapack_int Orientation<cusp::column_major>::type = LAPACK_COL_MAJOR;

template< typename TriangularFormat >
struct UpperOrLower {static const char type;};
template<>
const char UpperOrLower<upper>::type = 'U';
template<>
const char UpperOrLower<lower>::type = 'L';

template< typename DiagonalFormat >
struct UnitOrNonunit {static const char type;};
template<>
const char UnitOrNonunit<unit>::type    = 'U';
template<>
const char UnitOrNonunit<nonunit>::type = 'N';

template< typename JobType >
struct EvalsOrEvecs {static const char type;};
template<>
const char EvalsOrEvecs<evals>::type = 'N';
template<>
const char EvalsOrEvecs<evecs>::type = 'V';

template< typename OpType >
struct GenEigOp {static const char type;};
template<>
const char GenEigOp<gen_op1>::type = 1;
template<>
const char GenEigOp<gen_op2>::type = 2;
template<>
const char GenEigOp<gen_op3>::type = 3;

} // end namespace lapack
} // end namespace cusp


/* Siconos is a program dedicated to modeling, simulation and control
 * of non smooth dynamical systems.
 *
 * Copyright 2021 INRIA.
 *
 * 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 SiconosLAPACKE_H
#define SiconosLAPACKE_H

// IWYU pragma: private, include "SiconosLapack.h"
//#include "SiconosBlas.h"
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>

// -------- Headers and routines naming conventions for the different Lapack implementations --------

// --- Intel MKL Header ---
#if defined(HAS_MKL_LAPACKE)
#include <mkl_lapacke.h>
#else
// Standard lapacke header
#include <lapacke.h>
#endif

// Name of the routines
#define LAPACK_NAME(N) LAPACKE_##N

#define LA_TRANS 'T'
#define LA_NOTRANS 'N'
#define LA_UP 'U'
#define LA_LO 'L'
#define LA_NONUNIT 'N'
#define LA_UNIT 'U'

#define INTEGER(X) X
#define INTEGERP(X) X
#define CHAR(X) X

#ifndef lapack_int
#define lapack_int int
#endif

// --- DGESVD ---
#if defined(HAS_LAPACK_dgesvd)
#define WRAP_DGESVD(F,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,INFO)      \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12)
#else
#define WRAP_DGESVD(F,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,INFO)      \
  fprintf(stderr, "Your lapack version misses dgesvd function.\n");
#endif

// --- DGETRS ---
#define WRAP_DGETRS(F,A1,A2,A3,A4,A5,A6,A7,A8,INFO)   \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7,A8)

// --- DPOTRS ---
#define WRAP_DPOTRS(F,A1,A2,A3,A4,A5,A6,A7,INFO) \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7)

// --- DSYTRS ---
#define WRAP_DSYTRS(F,A1,A2,A3,A4,A5,A6,A7,A8,INFO) \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7,A8)

// --- DGESV ---
#define WRAP_DGESV(F,A1,A2,A3,A4,A5,A6,A7,INFO)   \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7)

// --- DPOSV ---
#define WRAP_DPOSV(F,A1,A2,A3,A4,A5,A6,A7,INFO) \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7)

// --- DGELS ---
#if defined(HAS_LAPACK_dgels)
#define WRAP_DGELS(F,A1,A2,A3,A4,A5,A6,A7,A8,INFO)    \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7,A8)
#else
#define WRAP_DGELS(F,A1,A2,A3,A4,A5,A6,A7,A8,INFO)                      \
  fprintf(stderr, "Your lapack version misses dgels function.\n");
#endif

// --- DGETRI ---
#define WRAP_DGETRI(F,A1,A2,A3,A4,INFO)         \
  INFO = F(CblasColMajor,A1,A2,A3,A4)

// --- DGETRF ---
#define WRAP_DGETRF(F,A1,A2,A3,A4,A5,INFO)  \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5)

// --- DPOTRF ---
#define WRAP_DPOTRF(F,A1,A2,A3,A4,INFO)  \
  INFO = F(CblasColMajor,A1,A2,A3,A4)

// --- DSYTRF ---
#define WRAP_DSYTRF(F,A1,A2,A3,A4,A5,INFO)      \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5)

// --- DTRTRS ---
#if defined(HAS_LAPACK_dtrtrs)
#define WRAP_DTRTRS(F,A1,A2,A3,A4,A5,A6,A7,A8,A9,INFO)  \
  INFO = F(CblasColMajor,A1,A2,A3,A4,A5,A6,A7,A8,A9)
#else
#define WRAP_DTRTRS(F,A1,A2,A3,A4,A5,A6,A7,A8,A9,INFO)                \
  fprintf(stderr, "Your lapack version misses dtrtrs function.\n");
#endif

#endif // SICONOSLAPACKE_H


/// @file
///
/// @brief test of gsl pointer wrappers
///
/// @copyright (c) 2007 CSIRO
/// Australia Telescope National Facility (ATNF)
/// Commonwealth Scientific and Industrial Research Organisation (CSIRO)
/// PO Box 76, Epping NSW 1710, Australia
/// atnf-enquiries@csiro.au
///
/// This file is part of the ASKAP software distribution.
///
/// The ASKAP software distribution 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
///
/// @author Max Voronkov <maxim.voronkov@csiro.au>

// unit test include
#include <cppunit/extensions/HelperMacros.h>

// own includes
#include "utils/SharedGSLTypes.h"

// gsl includes
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>

namespace askap {

namespace utility {

/// @brief helper specialisation to allow testing of the destructor call
/// @details It is not used anywhere outside this test suit
template<> struct CustomGSLDeleter<bool> {
    /// @brief this method just sets the passed object to true
    /// @param[in] obj pointer to a boolean variable
    void operator()(bool *obj) const { *obj = true; }
};

class SharedGSLTypesTest : public CppUnit::TestFixture
{
   CPPUNIT_TEST_SUITE(SharedGSLTypesTest);
   CPPUNIT_TEST(testVector);
   CPPUNIT_TEST(testMatrix);
   CPPUNIT_TEST_EXCEPTION(testNullPointer,AskapError);
   CPPUNIT_TEST(testDestruction);
   CPPUNIT_TEST_SUITE_END();
public:
   void testVector() {
      const size_t nElements = 10;
      SharedGSLVector vec = createGSLVector(nElements);
      for (size_t el = 0; el < nElements; ++el) {
           gsl_vector_set(vec.get(), el, double(el));
      }
      // check the content (can't really check that the destructor is
      // called)
      for (size_t el = 0; el < nElements; ++el) {
          CPPUNIT_ASSERT_DOUBLES_EQUAL(double(el), gsl_vector_get(vec.get(), el), 1e-6);
      }
      // destructor should be called on leaving this method
   }

   void testMatrix() {
      const size_t nRow = 10;
      const size_t nCol = 12;
      SharedGSLMatrix matr = createGSLMatrix(nRow, nCol);
      for (size_t row = 0; row < nRow; ++row) {
           for (size_t col = 0; col < nCol; ++col) {
                gsl_matrix_set(matr.get(), row, col, double(row*col));
           }
      }

      // check the content (can't really check that the destructor is
      // called)
      for (size_t row = 0; row < nRow; ++row) {
           for (size_t col = 0; col < nCol; ++col) {
                CPPUNIT_ASSERT_DOUBLES_EQUAL(double(row*col), gsl_matrix_get(matr.get(), row,col), 1e-6);
           }
      }
      // destructor should be called on leaving this method
   }

   void testNullPointer() {
      gsl_vector *nullVec = NULL;
      // the following should throw an exception
      createGSLObject(nullVec);
   }

   void testDestruction() {
      bool destructorCalledBuf = false;
      {
        const boost::shared_ptr<bool> destructorCalledPtr = createGSLObject(&destructorCalledBuf);
        CPPUNIT_ASSERT(destructorCalledPtr);
        CPPUNIT_ASSERT_EQUAL(false, *destructorCalledPtr);
        CPPUNIT_ASSERT_EQUAL(false, destructorCalledBuf);
      }
      CPPUNIT_ASSERT_EQUAL(true, destructorCalledBuf);
   }
};

} // namespace utility

} // namespace askap


/**
 * ex_gnuplot.h
 *
 * utility functions for plotting with gnuplot (v 4.6)
 *
 *  - minimizes boilerplate needed to use
 *  - popen -> process piping available, less temp files
 *  - idea based on myexamples/gnuplot (syntax) and gnuplot_i by N. Devillard (pipes), except a bit simpler
 *
 * Aaro Salosensaari 2016
 *
 * Original gnuplot_i was written by N. Devillard 1998 (version 2.10 2003) and is in public domain.
 */

#ifndef EX_GNUPLOT
#define EX_GNUPLOT

#include <stdarg.h>
#include <stdio.h>
#include <gsl/gsl_vector.h>
#include "ex_util.h"

#define GP_MAX_TMP_FILES 32

typedef struct {
    /* Pipe to gnuplot process */
    FILE *gnucmd ;

    /* Number of currently active plots */
    int nplots ;

    /* Pointer to table of names of temporary files */
    char *tmp_filename_tbl[GP_MAX_TMP_FILES] ;

    /* Number of temporary files */
    int ntmp ;
} gnuplot_ctrl ;

/**
 * Public interface function declarations
 */

gnuplot_ctrl *gnuplot_init(void);

void gnuplot_close(gnuplot_ctrl *handle);

/*
 * gnuplot_cmd
 *
 * This sends a string to an active gnuplot session, to be executed.
 * There is strictly no way to know if the command has been
 * successfully executed or not.
 * The command syntax is the same as printf.
 */
void gnuplot_cmd(gnuplot_ctrl *handle, char const *cmd, ...);

/*
 * ex_plot_xf can plot a variable number n of x, f(x) plots.
 *
 * n: number of plots
 * xlow:
 * xhigh:
 * xstep: (self-evident)
 * f1: function to plot
 * title1: title
 * style1: gnuplot style spef
 * (repeat fi, titlei, stylei until i=n)
 */
void ex_plot_xf(gnuplot_ctrl *handle, const int n, double xlow, double xhigh, double xstep, db_fx f1, const char *title1, const char *style1, ...);

/*
 * ex_plot_xys can plot a variable number n of x, y plots.
 *
 * n: number of plots
 * xdata: x points
 * ydata: y points
 * title1: title
 * style1: gnuplot style spef
 * (repeat xdatai, ydatai, titlei, stylei until i=n)
 */
void ex_plot_xys(gnuplot_ctrl *handle, const int n, gsl_vector *xdata1, gsl_vector *ydata1, const char *title1, const char *style1, ...);

void gnuplot_xy(gnuplot_ctrl *handle, gsl_vector *xdata, gsl_vector *ydata, const char *title1, const char *style1);

#endif /* ifndef EX_GNUPLOT */


/*! Implementation of the class `Solver`.
 * \file solver.h
 */

#if !defined(SOLVER_H)
#define SOLVER_H

#include <petsc.h>

/**
 * \class Solver
 * \brief Super class for an iterative solver.
 */
class Solver
{
public:
  virtual ~Solver(){ }
  virtual PetscErrorCode create(const Mat &A) = 0;
  virtual PetscErrorCode solve(Vec &x, Vec &b) = 0;
  virtual PetscErrorCode getIters(PetscInt &iters) = 0;

}; // Solver

#endif


/* $Id$ */
/*--------------------------------------------------------------------*/
/*;  Copyright (C) 2008                                               */
/*;  Associated Universities, Inc. Washington DC, USA.                */
/*;                                                                   */
/*;  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 Massachusetts Ave, Cambridge,     */
/*;  MA 02139, USA.                                                   */
/*;                                                                   */
/*; Correspondence about this software should be addressed as follows:*/
/*;         Internet email: bcotton@nrao.edu.                         */
/*;         Postal address: William Cotton                            */
/*;                         National Radio Astronomy Observatory      */
/*;                         520 Edgemont Road                         */
/*;                         Charlottesville, VA 22903-2475 USA        */
/*--------------------------------------------------------------------*/

#include "ObitUtil.h"
#include <gsl/gsl_multifit.h>

/*----------------Obit: Merx mollis mortibus nuper ------------------*/
/**
 * \file ObitUtil.c
 * ObitUtil utility function definitions.
 *
 */

/*----------------------Private functions---------------------------*/
/** Private: qsort ofloat comparison */
static int compare_gfloat  (const void* arg1,  const void* arg2);

/*----------------------Public functions---------------------------*/
/**
 * Get mean values of an array.
 * \param array array of values may be magic value blanked
 * \param n     dimension of array
 * \return      Mean value, possibly blanked
 */
ofloat meanValue (ofloat *array, olong incs, olong n)
{
  olong i, count;
  ofloat sum, fblank =ObitMagicF() ;

  if (n<=0) return fblank;
  sum = 0.0;
  count = 0;
    for (i=0; i<n; i++) {
      if (array[i*incs]!=fblank) {
	sum += array[i*incs];
	count++;
      }
    }
    if (count>0) sum /= count;
    else sum = fblank;

  return sum;
} /* end meanValue */

/**
 * Get median value of an array.
 * Does sort then returns value array[ngood/2]
 * \param array   array of values, on return will be in ascending order
 * \param n       dimension of array
 * \param inc     stride in array
 * \return        Median value, possibly blanked
 */
ofloat medianValue (ofloat *array, olong incs, olong n)
{
  olong i, ngood;
  ofloat out, fblank = ObitMagicF();
  gboolean blanked;

  if (n<=0) return fblank;

  /* Count good (non blank) points */
  ngood = 0;
  for (i=0; i<n; i++) if (array[i*incs]!=fblank) ngood++;
  if (ngood<1) return fblank;  /* Any good data? */
  blanked = ngood<n;   /* Anything blanked? */

  /* set blanked values to 2.0e20 for sort */
  for (i=0; i<n; i++) if (array[i*incs]==fblank) array[i*incs] = 2.0e20;

  /* Sort to ascending order */
  qsort ((void*)array, n, MAX(1,incs)*sizeof(ofloat), compare_gfloat);

  /* Set median */
  out = array[(ngood/2)*incs];

  /* reset blanked values */
  if (blanked) {
    for (i=0; i<n; i++) if (array[i*incs]>1.0e20) array[i*incs] = fblank;
  }

  return out;
} /* end medianValue */

/**
 * Return average of navg values around median (with magic value blanking)
 * Does sort then returns average of center of array
 * If there are fewer than navg valid data then the median is returned.
 * \param array array of values, on return will be in ascending order
 * \param incs  increment in data array, >1 => weights
 * \param navg  width of averaging about median
 * \param doWt  If True value after data is a weight
 * \param n     dimension of array
 * \return      Median/average, possibly blanked
 */
ofloat medianAvg (ofloat *array, olong incs, olong navg, gboolean doWt, olong n)
{
  olong i, cnt, wid, ngood, ind, hi, lo;
  ofloat temp, wt, out=0.0, fblank = ObitMagicF();
  ofloat center;
  gboolean blanked;

  if (n<=0) return fblank;

  /* Count good (non blank) points */
  ngood = 0;
  for (i=0; i<n; i++) if (array[i*incs]!=fblank) ngood++;
  if (ngood<1) return fblank;  /* Any good data? */
  blanked = ngood<n;   /* Anything blanked? */

  /* set blanked values to 2.0e20 for sort */
  for (i=0; i<n; i++) if (array[i*incs]==fblank) array[i*incs] = 2.0e20;

  /* Sort to ascending order */
  qsort ((void*)array, n, MAX(1,incs)*sizeof(ofloat), compare_gfloat);

  /* How many good values? */
  cnt = 0;
  for (i=0; i<n; i++) if (array[i*incs]<1.0e20) cnt++;
  wid = MIN (MAX (1, navg/2), ngood/2);  /* width of averaging */
  ngood = cnt;

  ind = (ngood-1)/2;  /* center good value */
  out = array[ind*incs];

  /* average central values */
  if (ngood>navg) {
    center = (ngood-1)/2.0;
    lo = MAX (1, (olong)(center - wid + 0.6));
    hi = MIN (ngood, (olong)(center + wid));
    /* Weighted? */
    if (doWt) {
      temp = 0.0; wt = 0.0;
      for (i=lo; i<=hi; i++) {
	if (array[i*incs]<1.0e20) {
	  wt += array[i*incs+1];
	  temp += array[i*incs]*array[i*incs+1];
	}
      }
      if (wt>0.0) out = temp/wt;
    } else { /* unweighted */
      temp = 0.0; cnt = 0;
      for (i=lo; i<=hi; i++) {
	if (array[i]<1.0e20) {
	  cnt++;
	  temp += array[i];
	}
	if (cnt>0) out = temp/cnt;
      }
    } /* end if weighting */
  }

  /* reset blanked values */
  if (blanked) {
    for (i=0; i<n; i++) if (array[i*incs]>1.0e20) array[i*incs] = fblank;
  }

  return out;
} /* end medianAvg */

/**
 * Return the running median of an array
 * \param n       Number of points
 * \param wind    Width of median window in cells
 * \param array   Array of values, fblank blanking supported
 * \param alpha   0 -> 1 = pure boxcar -> pure MWF (Alpha of the
 *                data samples in a window are discarded and
 *                the rest averaged).
 * \param rms     RMS of array, median average sigma from each wind of data
 * \param out     array of size of array to be with median values
 * \param work    work array of size of array
 */
void RunningMedian (olong n, olong wind, ofloat *array, ofloat alpha,
		    ofloat *RMS, ofloat *out, ofloat *work)
{
  ofloat *lwork=NULL;
  ofloat level, sigma, sigmaSum, sigmaCnt;
  ofloat fblank = ObitMagicF();
  olong i, j, k, op, ind, half, RMScnt=0;

  /* Create array */
  lwork = g_malloc0(wind*sizeof(ofloat));

  half = wind/2;
  ind  = 0;
  op   = 0;
  k    = 0;
  sigmaSum = 0.0;
  sigmaCnt = 1;

  /* First half wind filled with median of first wind points */
  for (j=ind; j<ind+wind; j++) lwork[k++] = array[j];
  ind++;
  level = MedianLevel (wind, lwork, alpha);
  sigma = MedianSigma (wind, lwork, level);
  if (sigma!=fblank) {
    sigmaSum = sigma;
    sigmaSum += sigma;
    work[RMScnt++] = sigma;
  }
  for (k=0; k<half; k++) out[op++] = level;

  /* Loop over middle of array */
  for (i=half; i<n-half; i++) {
    k = 0;
    for (j=ind; j<ind+wind; j++) lwork[k++] = array[j];
    ind++;
    level = MedianLevel (wind, lwork, alpha);
    sigma = MedianSigma (wind, lwork, level);
    if (sigma!=fblank) {
      sigmaSum += sigma;
      sigmaCnt++;
      work[RMScnt++] = sigma;
    }
    out[op++] = level;
  } /* end loop over array */

  /* Fill in bit at end */
  while (op<n) {
    out[op++] = level;
  }

  /* median Average sigmas */
  *RMS = MedianLevel (RMScnt, work, alpha);

  /* Cleanup */
  if (lwork) g_free(lwork);

} /* end RunningMedian */

/**
 * Determine alpha median value of a ofloat array
 * \param n       Number of points
 * \param value   Array of values
 * \param alpha   0 -> 1 = pure boxcar -> pure MWF (ALPHA of the
 *                data samples are discarded and the rest averaged).
 * \return alpha median value
 */
ofloat MedianLevel (olong n, ofloat *value, ofloat alpha)
{
  ofloat out=0.0;
  ofloat fblank = ObitMagicF();
  ofloat beta, sum;
  olong i, i1, i2, count;

  if (n<=0) return out;

  /* Sort to ascending order */
  qsort ((void*)value, n, sizeof(ofloat), compare_gfloat);

  out = value[n/2];

  beta = MAX (0.05, MIN (0.95, alpha)) / 2.0; /*  Average around median factor */

  /* Average around the center */
  i1 = MAX (0, (n/2)-(olong)(beta*n+0.5));
  i2 = MIN (n, (n/2)+(olong)(beta*n+0.5));

  if (i2>i1) {
    sum = 0.0;
    count = 0;
    for (i=i1; i<i2; i++) {
      if (value[i]!=fblank) {
	sum += value[i];
	count++;
      }
    }
    if (count>0) out = sum / count;
  }

  return out;
} /* end MedianLevel */

/**
 * Determine robust RMS value of a ofloat array about mean
 * Use center 90% of points, excluding at least one point from each end
 * \param n       Number of points, needs at least 4
 * \param value   Array of values assumed sorted
 * \param mean    Mean value of value
 * \return RMS value, fblank if cannot determine
 */
ofloat MedianSigma (olong n, ofloat *value, ofloat mean)
{
  ofloat fblank = ObitMagicF();
  ofloat out;
  ofloat sum;
  olong i, i1, i2, count;

  out = fblank;
  if (n<=4) return out;
  if (mean==fblank) return out;

  /* Get RMS around the center 90% */
  i1 = MAX (1,   (n/2)-(olong)(0.45*n+0.5));
  i2 = MIN (n-1, (n/2)+(olong)(0.45*n+0.5));

  if (i2>i1) {
    sum = 0.0;
    count = 0;
    for (i=i1; i<i2; i++) {
      if (value[i]!=fblank) {
	sum += (value[i]-mean)*(value[i]-mean);
	count++;
      }
    }
    if (count>1) out = sqrt(sum / (count-1));
  }

  return out;
} /* end MedianSigma */

/**
 * Fit polynomial y = f(poly, x) with magic value blanking
 * Use gsl package.
 * \param poly   [out] polynomial coef in order of increasing power of x
 * \param order  order of the polynomial
 * \param x      values at which y is sampled
 * \param y      values to be fitted
 * \param wt     weights for values
 * \param n      number of (x,y) value pairs
 */
void  FitPoly (ofloat *poly, olong order, ofloat *x, ofloat *y, ofloat *wt,
	       olong n)
{
  olong i, j, k, good, p=order+1;
  ofloat fgood=0.0;
  double xi, chisq;
  gsl_matrix *X, *cov;
  gsl_vector *yy, *w, *c;
  gsl_multifit_linear_workspace *work;
  ofloat fblank = ObitMagicF();

  /* Only use good data */
  good = 0;
  for (i=0; i<n; i++) if (y[i]!=fblank) {good++; fgood = y[i];}

  /* If only one good datum - use it */
  if (good==1) {
    poly[0] = fgood;
    poly[1] = 0.0;
  }

  if (good<(order+1)) {  /* Not enough good data */
    poly[0] = fblank;
    return;
  }

  /* If only one and order=0 use it */
  if ((good==1) && (order==0)) {
    poly[0] = y[0];
    return;
  }

  /* allocate arrays */
  X    = gsl_matrix_alloc(good, p);
  yy   = gsl_vector_alloc(good);
  w    = gsl_vector_alloc(good);
  c    = gsl_vector_alloc(p);
  cov  = gsl_matrix_alloc(p, p);
  work = gsl_multifit_linear_alloc (good, p);

  /* set data */
  k = 0;
  for (i=0; i<n; i++) {
    if (y[i]!=fblank) {
      gsl_vector_set(yy, k, (double)y[i]);
      gsl_vector_set(w, k, (double)wt[i]);
      xi = 1.0;
      for (j=0; j<p; j++) {
	gsl_matrix_set(X, k, j, xi);
	xi *= x[i];
      }
      k++;  /* good ones */
    }
  }

  /* Fit */
  gsl_multifit_wlinear (X, w, yy, c, cov, &chisq, work);

  /* get results */
  for (j=0; j<p; j++) poly[j] = (ofloat)gsl_vector_get(c, j);

  /* Deallocate arrays */
  gsl_matrix_free(X);
  gsl_vector_free(yy);
  gsl_vector_free(w);
  gsl_vector_free(c);
  gsl_matrix_free(cov);
  gsl_multifit_linear_free (work);
} /* end FitPoly */

/**
 * Evaluate polynomial y = f(poly, x)
 * \param order  order of the polynomial
 * \param poly   polynomial coef in order of increasing power of x
 * \param x      value at which polynomial is to be evaluated
 * \return evaluated polynomial
 */
ofloat  EvalPoly (olong order, ofloat *poly, ofloat x)
{
  olong i;
  ofloat sum = 0.0, aarg = 1.0;

  for (i=0; i<=order; i++) {
    sum  += poly[i]*aarg;
    aarg *= x;
  }
  return sum;
} /* end EvalPoly  */

/*----------------------Private functions---------------------------*/
/**
 * ofloat comparison of two arguments
 * \param arg1 first value to compare
 * \param arg2 second value to compare
 * \return negative if arg1 is less than arg2, zero if equal
 *  and positive if arg1 is greater than arg2.
 */
static int compare_gfloat  (const void* arg1,  const void* arg2)
{
  int out = 0;
  ofloat larg1, larg2;

  larg1 = *(ofloat*)arg1;
  larg2 = *(ofloat*)arg2;
  if (larg1<larg2) out = -1;
  else if (larg1>larg2) out = 1;
  return out;
} /* end compare_gfloat */


#ifndef AMICI_VECTOR_H
#define AMICI_VECTOR_H

#include <vector>
#include <type_traits>

#include <amici/exception.h>

#include <nvector/nvector_serial.h>

#include <gsl/gsl-lite.hpp>

namespace amici {

/** Since const N_Vector is not what we want */
using const_N_Vector =
    std::add_const<typename std::remove_pointer<N_Vector>::type *>::type;

/** AmiVector class provides a generic interface to the NVector_Serial struct */
class AmiVector {
  public:
    /**
     * @brief Default constructor
     */
    AmiVector() = default;

    /** Creates an std::vector<realtype> and attaches the
     * data pointer to a newly created N_Vector_Serial.
     * Using N_VMake_Serial ensures that the N_Vector
     * module does not try to deallocate the data vector
     * when calling N_VDestroy_Serial
     * @brief empty constructor
     * @param length number of elements in vector
     */
    explicit AmiVector(const long int length)
        : vec_(static_cast<decltype(vec_)::size_type>(length), 0.0),
          nvec_(N_VMake_Serial(length, vec_.data())) {}

    /** Moves data from std::vector and constructs an nvec that points to the
     * data
     * @brief constructor from std::vector,
     * @param rvec vector from which the data will be moved
     */
    explicit AmiVector(std::vector<realtype> rvec)
        : vec_(std::move(rvec)),
          nvec_(N_VMake_Serial(static_cast<long int>(vec_.size()), vec_.data())) {}

    /** Copy data from gsl::span and constructs a vector
     * @brief constructor from gsl::span,
     * @param rvec vector from which the data will be copied
     */
    explicit AmiVector(gsl::span<realtype> rvec)
        : AmiVector(std::vector<realtype>(rvec.begin(), rvec.end())) {}

    /**
     * @brief copy constructor
     * @param vold vector from which the data will be copied
     */
    AmiVector(const AmiVector &vold) : vec_(vold.vec_) {
        nvec_ =
            N_VMake_Serial(static_cast<long int>(vold.vec_.size()), vec_.data());
    }

    /**
     * @brief move constructor
     * @param other vector from which the data will be moved
     */
    AmiVector(AmiVector&& other) noexcept : nvec_(nullptr) {
        vec_ = std::move(other.vec_);
        synchroniseNVector();
    }

    /**
     * @brief destructor
     */
    ~AmiVector();

    /**
     * @brief copy assignment operator
     * @param other right hand side
     * @return left hand side
     */
    AmiVector &operator=(AmiVector const &other);

    /**
     * @brief data accessor
     * @return pointer to data array
     */
    realtype *data();

    /**
     * @brief const data accessor
     * @return const pointer to data array
     */
    const realtype *data() const;

    /**
     * @brief N_Vector accessor
     * @return N_Vector
     */
    N_Vector getNVector();

    /**
     * @brief N_Vector accessor
     * @return N_Vector
     */
    const_N_Vector getNVector() const;

    /**
     * @brief Vector accessor
     * @return Vector
     */
    std::vector<realtype> const &getVector() const;

    /**
     * @brief returns the length of the vector
     * @return length
     */
    int getLength() const;

    /**
     * @brief resets the Vector by filling with zero values
     */
    void reset();

    /**
     * @brief changes the sign of data elements
     */
    void minus();

    /**
     * @brief sets all data elements to a specific value
     * @param val value for data elements
     */
    void set(realtype val);

    /**
     * @brief accessor to data elements of the vector
     * @param pos index of element
     * @return element
     */
    realtype &operator[](int pos);
    /**
     * @brief accessor to data elements of the vector
     * @param pos index of element
     * @return element
     */
    realtype &at(int pos);

    /**
     * @brief accessor to data elements of the vector
     * @param pos index of element
     * @return element
     */
    const realtype &at(int pos) const;

    /**
     * @brief copies data from another AmiVector
     * @param other data source
     */
    void copy(const AmiVector &other);

  private:
    /** main data storage */
    std::vector<realtype> vec_;

    /** N_Vector, will be synchronised such that it points to data in vec */
    N_Vector nvec_ {nullptr};

    /**
     * @brief reconstructs nvec such that data pointer points to vec data array
     */
    void synchroniseNVector();
};

/**
 * @brief AmiVectorArray class.
 *
 * Provides a generic interface to arrays of NVector_Serial structs
 */
class AmiVectorArray {
  public:
    /**
     * @brief Default constructor
     */
    AmiVectorArray() = default;

    /**
     * Creates an std::vector<realype> and attaches the
     * data pointer to a newly created N_VectorArray
     * using CloneVectorArrayEmpty ensures that the N_Vector
     * module does not try to deallocate the data vector
     * when calling N_VDestroyVectorArray_Serial
     * @brief empty constructor
     * @param length_inner length of vectors
     * @param length_outer number of vectors
     */
    AmiVectorArray(long int length_inner, long int length_outer);

    /**
     * @brief copy constructor
     * @param vaold object to copy from
     */
    AmiVectorArray(const AmiVectorArray &vaold);

    ~AmiVectorArray() = default;

    /**
     * @brief copy assignment operator
     * @param other right hand side
     * @return left hand side
     */
    AmiVectorArray &operator=(AmiVectorArray const &other);

    /**
     * @brief accessor to data of AmiVector elements
     * @param pos index of AmiVector
     * @return pointer to data array
     */
    realtype *data(int pos);

    /**
     * @brief const accessor to data of AmiVector elements
     * @param pos index of AmiVector
     * @return const pointer to data array
     */
    const realtype *data(int pos) const;

    /**
     * @brief accessor to elements of AmiVector elements
     * @param ipos inner index in AmiVector
     * @param jpos outer index in AmiVectorArray
     * @return element
     */
    realtype &at(int ipos, int jpos);

    /**
     * @brief const accessor to elements of AmiVector elements
     * @param ipos inner index in AmiVector
     * @param jpos outer index in AmiVectorArray
     * @return element
     */
    const realtype &at(int ipos, int jpos) const;

    /**
     * @brief accessor to NVectorArray
     * @return N_VectorArray
     */
    N_Vector *getNVectorArray();

    /**
     * @brief accessor to NVector element
     * @param pos index of corresponding AmiVector
     * @return N_Vector
     */
    N_Vector getNVector(int pos);

    /**
     * @brief const accessor to NVector element
     * @param pos index of corresponding AmiVector
     * @return N_Vector
     */
    const_N_Vector getNVector(int pos) const;

    /**
     * @brief accessor to AmiVector elements
     * @param pos index of AmiVector
     * @return AmiVector
     */
    AmiVector &operator[](int pos);

    /**
     * @brief const accessor to AmiVector elements
     * @param pos index of AmiVector
     * @return const AmiVector
     */
    const AmiVector &operator[](int pos) const;

    /**
     * @brief length of AmiVectorArray
     * @return length
     */
    int getLength() const;

    /**
     * @brief resets every AmiVector in AmiVectorArray
     */
    void reset();

    /**
     * @brief flattens the AmiVectorArray to a vector in row-major format
     * @param vec vector into which the AmiVectorArray will be flattened. Must
     * have length equal to number of elements.
     */
    void flatten_to_vector(std::vector<realtype> &vec) const;

    /**
     * @brief copies data from another AmiVectorArray
     * @param other data source
     */
    void copy(const AmiVectorArray &other);

  private:
    /** main data storage */
    std::vector<AmiVector> vec_array_;

    /**
     * N_Vector array, will be synchronised such that it points to
     * respective elements in the vec_array
     */
    std::vector<N_Vector> nvec_array_;
};

} // namespace amici

namespace gsl {
/**
 * @brief Create span from N_Vector
 * @param nv
 * @return
 */
inline span<realtype> make_span(N_Vector nv)
{
    return span<realtype>(N_VGetArrayPointer(nv), N_VGetLength_Serial(nv));
}
} // namespace gsl

#endif /* AMICI_VECTOR_H */


/******************************************************************************
 * Copyright (C) 2016-2019, Cris Cecka.  All rights reserved.
 * Copyright (C) 2016-2019, NVIDIA CORPORATION.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the name of the NVIDIA CORPORATION 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 NVIDIA CORPORATION 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.
 ******************************************************************************/

#pragma once

#include <blam/detail/config.h>

#if !defined(__MKL_CBLAS_H__)
#include <cblas.h>
using CBLAS_LAYOUT = CBLAS_ORDER;
#endif

namespace blam
{

inline CBLAS_LAYOUT
cblas_type(Layout order)
{
  switch (order) {
    case Layout::ColMajor: return CblasColMajor;
    case Layout::RowMajor: return CblasRowMajor;
    default:
      assert(false && "Invalid Layout Parameter");
      return CblasColMajor;
  }
}

inline CBLAS_TRANSPOSE
cblas_type(Op trans)
{
  switch (trans) {
    case Op::NoTrans:   return CblasNoTrans;
    case Op::Trans:     return CblasTrans;
    case Op::ConjTrans: return CblasConjTrans;
    default:
      assert(false && "Invalid Op Parameter");
      return CblasNoTrans;
  }
}

inline CBLAS_UPLO
cblas_type(Uplo uplo)
{
  switch (uplo) {
    case Uplo::Upper: return CblasUpper;
    case Uplo::Lower: return CblasLower;
    default:
      assert(false && "Invalid Uplo Parameter");
      return CblasUpper;
  }
}

inline CBLAS_SIDE
cblas_type(Side side)
{
  switch (side) {
    case Side::Left:  return CblasLeft;
    case Side::Right: return CblasRight;
    default:
      assert(false && "Invalid Side Parameter");
      return CblasLeft;
  }
}

inline CBLAS_DIAG
cblas_type(Diag diag)
{
  switch (diag) {
    case Diag::Unit:    return CblasUnit;
    case Diag::NonUnit: return CblasNonUnit;
    default:
      assert(false && "Invalid Diag Parameter");
      return CblasUnit;
  }
}

} // end namespace blam