// Copyright 2012 Jesse Windle - jesse.windle@gmail.com

// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see
// <http://www.gnu.org/licenses/>.

/*********************************************************************

  This class wraps GSL's random number generator and random
  distribution functions into a class.  We use the Mersenne Twister
  for random number generation since it has a large period, which is
  what we want for MCMC simulation.

  When compiling include -lgsl -lcblas -llapack .

*********************************************************************/

#ifndef __BASICRNG__
#define __BASICRNG__

#include <stdio.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_cdf.h>
#include <gsl/gsl_sf.h>

#include <iostream>
#include <fstream>
#include <ctime>
#include <string>
#include <cmath>

using std::string;
using std::ofstream;
using std::ifstream;

//////////////////////////////////////////////////////////////////////
			      // RNG //
//////////////////////////////////////////////////////////////////////

class BasicRNG {

 protected:

  gsl_rng * r;

 public:

  // Constructors and destructors.
  BasicRNG(unsigned long seed);

  virtual ~BasicRNG()
    { gsl_rng_free (r); }

  // Assignment=
  BasicRNG& operator=(const BasicRNG& rng);

  // Read / Write / Set
  bool read (const string& filename);
  bool write(const string& filename);
  void set(unsigned long seed);

  // Get rng -- be careful.  Needed for other random variates.
  gsl_rng* getrng() { return r; }

  // Random variates.
  double unif  ();                             // Uniform
  double expon_mean(double mean);     // Exponential
  double expon_rate(double rate);                  // Exponential
  double chisq (double df);                    // Chisq
  double norm  (double sd);                    // Normal
  double norm  (double mean , double sd);      // Normal
  double gamma_scale (double shape, double scale); // Gamma_Scale
  double gamma_rate  (double shape, double rate);  // Gamma_Rate
  double igamma(double shape, double scale);   // Inv-Gamma
  double flat  (double a=0  , double b=1  );   // Flat
  double beta  (double a=1.0, double b=1.0);   // Beta

  int bern  (double p);                     // Bernoulli

  // CDF
  static double p_norm (double x, int use_log=0);
  static double p_gamma_rate(double x, double shape, double rate, int use_log=0);

  // Density
  static double d_beta(double x, double a, double b);

  // Utility
  static double Gamma (double x, int use_log=0);

}; // BasicRNG

#endif

////////////////////////////////////////////////////////////////////////////////
				 // APPENDIX //
////////////////////////////////////////////////////////////////////////////////

// If you make everything inline within the same translation unit then that
// function will not be callable from anohter translation unit.  You can see
// that the function is missing by using the nm command.


/*
 *  specialfunctionsmodule.h
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST 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.
 *
 *  NEST 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 NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef SPECIALFUNCTIONSMODULE_H
#define SPECIALFUNCTIONSMODULE_H
/*
    SLI Module implementing functions from the GNU Science Library.
    The GSL is available from sources.redhat.com/gsl.
*/

/*
    NOTE: Special functions are available only if the GSL is installed.
          If no GSL is available, calling special functions will result
          in a SLI error message.  HEP 2002-09-19.
*/

// Generated includes:
#include "config.h"

// Includes from sli:
#include "slifunction.h"
#include "slimodule.h"

#ifdef HAVE_GSL
// External include:
#include <gsl/gsl_integration.h>
#endif

// NOTE: all gsl headers are included in specialfunctionsmodule.cc

class SpecialFunctionsModule : public SLIModule
{

  // Part 1: Methods pertaining to the module ----------------------

public:
  SpecialFunctionsModule( void ){};
  // ~SpecialFunctionsModule(void);

  // The Module is registered by a call to this Function:
  void init( SLIInterpreter* );

  // This function will return the name of our module:
  const std::string name( void ) const;

  // Part 2: Classes for the implemented functions -----------------

public:
  /**
   * Classes which implement the GSL Funktions.
   * These must be public, since we want to export
   * objects of these.
   */
  class GammaIncFunction : public SLIFunction
  {
  public:
    GammaIncFunction()
    {
    }
    void execute( SLIInterpreter* ) const;
  };
  class LambertW0Function : public SLIFunction
  {
  public:
    LambertW0Function()
    {
    }
    void execute( SLIInterpreter* ) const;
  };
  class LambertWm1Function : public SLIFunction
  {
  public:
    LambertWm1Function()
    {
    }
    void execute( SLIInterpreter* ) const;
  };

  class ErfFunction : public SLIFunction
  {
  public:
    ErfFunction()
    {
    }
    void execute( SLIInterpreter* ) const;
  };

  class ErfcFunction : public SLIFunction
  {
  public:
    ErfcFunction()
    {
    }
    void execute( SLIInterpreter* ) const;
  };

  class GaussDiskConvFunction : public SLIFunction
  {
  public:
    void execute( SLIInterpreter* ) const;

    // need constructor and destructor to set up integration workspace
    GaussDiskConvFunction( void );
    ~GaussDiskConvFunction( void );

  private:
    // quadrature parameters, see GSL Reference
    static const int MAX_QUAD_SIZE;
    static const double QUAD_ERR_LIM;
    static const double QUAD_ERR_SCALE;

// integration workspace
#ifdef HAVE_GSL
    gsl_integration_workspace* w_;

    /**
     * Integrand function.
     * @note This function must be static with C linkage so that it can
     *       be passed to the GSL. Alternatively, one could define it
     *       outside the class.
     */
    static double f_( double, void* );
    static gsl_function F_; // GSL wrapper struct for it
#endif
  };

  // Part 3: One instatiation of each new function class -----------

public:
  const GammaIncFunction gammaincfunction;
  const LambertW0Function lambertw0function;
  const LambertWm1Function lambertwm1function;
  const ErfFunction erffunction;
  const ErfcFunction erfcfunction;
  const GaussDiskConvFunction gaussdiskconvfunction;

  // Part 3b: Internal variables
private:
};

// Part 4: Documentation for all functions -------------------------

/* BeginDocumentation

Name: Gammainc - incomplete gamma function

Synopsis: x a Gammainc -> result

Description: Computes the incomplete Gamma function
             int(t^(a-1)*exp(-t), t=0..x) / Gamma(a)

Parameters:  x      (double): upper limit of integration
             a      (double): order of Gamma function

Examples: 2.2 1.5 Gammainc -> 0.778615

Author: H E Plesser

FirstVersion: 2001-07-26

Remarks: This is the incomplete Gamma function P(a,x) defined as no. 6.5.1
         in Abramowitz&Stegun.  Requires the GSL.

References: http://sources.redhat.com/gsl/ref
*/

/* BeginDocumentation

Name: Erf - error function

Synopsis: x Erf -> result

Description: Computes the error function
             erf(x) = 2/sqrt(pi) int_0^x dt exp(-t^2)

Parameters:  x (double): error function argument

Examples: 0.5 erf -> 0.5205

Author: H E Plesser

FirstVersion: 2001-07-30

Remarks: Requires the GSL.

References: http://sources.redhat.com/gsl/ref

SeeAlso: Erfc
*/

/* BeginDocumentation

Name: Erfc - complementary error function

Synopsis: x Erfc -> result

Description: Computes the error function
             erfc(x) = 1 - erf(x) = 2/sqrt(pi) int_x^inf dt exp(-t^2)

Parameters:  x (double): error function argument

Examples: 0.5 erfc -> 0.4795

Author: H E Plesser

FirstVersion: 2001-07-30

Remarks: Requires the GSL.

References: http://sources.redhat.com/gsl/ref

SeeAlso: Erf
*/

/* BeginDocumentation

Name:GaussDiskConv - Convolution of a Gaussian with an excentric disk

Synopsis:R r0 GaussDiskConv -> result

Description:Computes the convolution of an excentric normalized Gaussian
with a disk

       C[R, r0] = IInt[ disk(rvec; R) * Gauss(rvec - r_0vec) d^2rvec ]
                = 2 Int[ r Exp[-r0^2-r^2] I_0[2 r r_0] dr, r=0..R]

Parameters:R   radius of the disk, centered at origin
r0  distance of Gaussian center from origin

Examples:SLI ] 3.2 2.3 GaussDiskConv =
0.873191

Author:H E Plesser

FirstVersion: 2002-07-12

Remarks:This integral is needed to compute the response of a DOG model to
 excentric light spots, see [1].  For technicalities, see [2].  Requires GSL.

References: [1] G. T. Einevoll and P. Heggelund, Vis Neurosci 17:871-885 (2000).
 [2] Hans E. Plesser, Convolution of an Excentric Gaussian with a Disk,
     Technical Report, arken.nlh.no/~itfhep, 2002

*/

#endif


/* bcls.c
   $Revision: 282 $ $Date: 2006-12-17 17:38:00 -0800 (Sun, 17 Dec 2006) $

   ---------------------------------------------------------------------
   This file is part of BCLS (Bound-Constrained Least Squares).

   Copyright (C) 2006 Michael P. Friedlander, Department of Computer
   Science, University of British Columbia, Canada. All rights
   reserved. E-mail: <mpf@cs.ubc.ca>.

   BCLS is 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.1 of the
   License, or (at your option) any later version.

   BCLS 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 BCLS; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
   USA
   ---------------------------------------------------------------------
*/
/*!
  \file
  BCLS user-callable library routines.
*/

#include <cblas.h>
#include <stdlib.h>
#include <stdio.h>
#include <float.h>
#include <math.h>
#include <assert.h>
#include <setjmp.h>

#include "bcls.h"
#include "bclib.h"
#include "bcsolver.h"
#include "bcversion.h"

/*!

  \brief Malloc wrapper.  Private to this file.

  Pointer to the newly allocated memory,
  or NULL if malloc returned an eror.

  \param[in]  len   Number of elements needed
  \param[in]  size  Memory needed for each element.
  \param[in]  who   Short character description of the memory needed.

  \return Pointer to the newly allocated memory.

*/
static void *
xmalloc (int len, size_t size, char * who)
{
    register void *value = malloc(len * size);
    if (value == NULL)
	fprintf( stderr, "Not enough memory to allocate %s.\n", who );
    return value;
}

/*!

  \brief Create a BCLS problem instance.

  This routine create a BCLS problem.  It *must* be called before any
  other BCLS routine.  It will create and initialize a new BCLS
  problem with enough space to accomodate the specified problem.  The
  problem is then initialized using bcls_init_prob.

  In order to re-initialize the BCLS problem instance, but not
  deallocate memory that's already sufficient for an equal or smaller
  size problem, use bcls_init_prob.

  \param[in] mmax  Maximum number of rows    in any A.
  \param[in] nmax  Maximum number of columns in any A.

  \return Return a pointer to a new BCLS problem.  If the return is
  NULL, then the initialization failed.

*/
BCLS *
bcls_create_prob( int mmax, int nmax )
{
    int mnmax = imax( nmax, mmax );

    assert( mmax > 0 && nmax > 0 );

    // Allocate the problem.
    BCLS *ls = (BCLS *)xmalloc(1, sizeof(BCLS), "ls" );

    // Check if the problem has been successfully allocated.
    if (ls == NULL) {
	fprintf( stderr, "XXX Could not allocate a BCLS problem.\n");
	return ls;
    }

    // -----------------------------------------------------------------
    // Allocate workspace vectors large enough to accomdate the problem.
    // -----------------------------------------------------------------

    // Record the maximum alloted workspace.
    ls->mmax = mmax;
    ls->nmax = nmax;

    // Residual: r(m+n).
    ls->r = (double *)xmalloc( mmax+nmax, sizeof(double), "r" );
    if (ls->r == NULL) goto error;

    // Gradient: g(n).
    ls->g = (double *)xmalloc( nmax, sizeof(double), "g" );
    if (ls->g == NULL) goto error;

    // Search direction, full space: dx(n).
    ls->dx = (double *)xmalloc( nmax, sizeof(double), "dx" );
    if (ls->dx == NULL) goto error;

    // Search direction, subspace: dxFree(n).
    ls->dxFree = (double *)xmalloc( nmax, sizeof(double), "dxFree" );
    if (ls->dxFree == NULL) goto error;

    // Step to each breakpoint: aBreak(n).
    ls->aBreak = (double *)xmalloc( nmax, sizeof(double), "aBreak" );
    if (ls->aBreak == NULL) goto error;

    // Indices of each breakpoint: iBreak(n).
    ls->iBreak = (int *)xmalloc( nmax, sizeof(int), "iBreak" );
    if (ls->iBreak == NULL) goto error;

    // Variable indices: ix(n).
    ls->ix = (int *)xmalloc( nmax, sizeof(int), "ix" );
    if (ls->ix == NULL) goto error;

    // Workspace: wrk_v( max(n, m) ).
    ls->wrk_u = (double *)xmalloc( mnmax, sizeof(double), "wrk_u" );
    if (ls->wrk_u == NULL) goto error;

    // Workspace: wrk_v( max(n, m) ).
    ls->wrk_v = (double *)xmalloc( mnmax, sizeof(double), "wrk_v" );
    if (ls->wrk_v == NULL) goto error;

    // Workspace: wrk_w( max(n, m) ).
    ls->wrk_w = (double *)xmalloc( mnmax, sizeof(double), "wrk_w" );
    if (ls->wrk_w == NULL) goto error;

    // -----------------------------------------------------------------
    // Initialize this new problem instance.
    // -----------------------------------------------------------------
    bcls_init_prob( ls );

    // -----------------------------------------------------------------
    // Exits.
    // -----------------------------------------------------------------

    // Successfull exit.
    return ls;

 error:
    // Unsuccessful exit.
    bcls_free_prob( ls );
    return ls;
}

/*!

  \brief Initialize a BCLS problem.

  Initialize a BCLS problem.  You can call this routine to "reset" a
  BCLS problem, i.e., reset all parameter values.  Note that it is
  automatically called by bcls_create_prob.

  \param[in,out] ls  BCLS problem context.

*/
void
bcls_init_prob( BCLS *ls )
{
    // Some quick error checking.
    assert( ls->mmax > 0 && ls->nmax > 0 );

    // Check if the problem has been successfully allocated.
    if (ls == NULL) {
	fprintf( stderr, "XXX The BCLS problem is NULL.\n");
	return;
    }

    // Initialize the problem structure.
    ls->print_info    =  NULL;
    ls->print_hook    =  NULL;
    ls->fault_info    =  NULL;
    ls->fault_hook    =  NULL;
    ls->Aprod         =  NULL;
    ls->Usolve        =  NULL;
    ls->CallBack      =  NULL;
    ls->UsrWrk        =  NULL;
    ls->anorm         =  NULL;
    ls->print_level   =  1;
    ls->proj_search   =  BCLS_PROJ_SEARCH_EXACT;
    ls->newton_step   =  BCLS_NEWTON_STEP_LSQR;
    ls->minor_file    =  NULL;
    ls->itnMaj        =  0;
    ls->itnMajLim     =  5  * (ls->nmax);
    ls->itnMin        =  0;
    ls->itnMinLim     =  10 * (ls->nmax);
    ls->nAprodT       =  0;
    ls->nAprodF       =  0;
    ls->nAprod1       =  0;
    ls->nUsolve       =  0;
    ls->m             =  0;
    ls->n             =  0;
    ls->unconstrained =  0;
    ls->damp          =  0.0;
    ls->damp_min      =  1.0e-4;
    ls->exit          =  BCLS_EXIT_UNDEF;
    ls->soln_rNorm    = -1.0;
    ls->soln_dInf     =  0.0;
    ls->soln_jInf     = -1;
    ls->soln_stat     =  BCLS_SOLN_UNDEF;
    ls->optTol        =  1.0e-6;
    ls->conlim        =  1.0 / ( 10.0 * sqrt( DBL_EPSILON ) );
    ls->mu            =  1.0e-2;
    ls->backtrack     =  1.0e-1;
    ls->backtrack_limit = 10;

    // Initialize timers.
    bcls_timer( &(ls->stopwatch[BCLS_TIMER_TOTAL] ),  BCLS_TIMER_INIT );
    ls->stopwatch[BCLS_TIMER_TOTAL].name = "Total time";

    bcls_timer( &(ls->stopwatch[BCLS_TIMER_APROD] ),  BCLS_TIMER_INIT );
    ls->stopwatch[BCLS_TIMER_APROD].name = "Total time for Aprod";

    bcls_timer( &(ls->stopwatch[BCLS_TIMER_USOLVE] ),  BCLS_TIMER_INIT );
    ls->stopwatch[BCLS_TIMER_USOLVE].name = "Total time for Usolve";

    bcls_timer( &(ls->stopwatch[BCLS_TIMER_LSQR] ),  BCLS_TIMER_INIT );
    ls->stopwatch[BCLS_TIMER_LSQR].name = "Total time for LSQR";

    // Initialize constants.
    ls->eps         = DBL_EPSILON;
    ls->eps2        = pow( DBL_EPSILON, 0.50 );
    ls->eps3        = pow( DBL_EPSILON, 0.75 );
    ls->epsx        = ls->eps3;
    ls->epsfixed    = DBL_EPSILON;
    ls->BigNum      = BCLS_INFINITY;

    return;
}

/*!

  \brief Free and reinitialize all resources in an existing BCLS problem.

  \return
  - 0: no errors
  - 1: the BCLS problem is NULL (ie, does not exist).

*/
int
bcls_free_prob( BCLS *ls )
{
    // Report an error if it's already NULL.
    if (ls == NULL) return 1;

    // Deallocate the workspace.
    assert( ls->r       != NULL ); free( ls->r       );
    assert( ls->g       != NULL ); free( ls->g       );
    assert( ls->dx      != NULL ); free( ls->dx      );
    assert( ls->dxFree  != NULL ); free( ls->dxFree  );
    assert( ls->aBreak  != NULL ); free( ls->aBreak  );
    assert( ls->iBreak  != NULL ); free( ls->iBreak  );
    assert( ls->ix      != NULL ); free( ls->ix      );
    assert( ls->wrk_u   != NULL ); free( ls->wrk_u   );
    assert( ls->wrk_v   != NULL ); free( ls->wrk_v   );
    assert( ls->wrk_w   != NULL ); free( ls->wrk_w   );

    // Deallocate the BCLS problem.
    free( ls );

    return 0;
}

/*!

  \brief Install a print-hook routine.

  This routine installs a user-defined print-hook routine.

  The parameter info is a transit pointer passed to the hook routine.

  The parameter hook is an entry point to the user-defined print-hook
  routine. This routine is called by the routine "print" every time an
  informative message should be output. The routine "print" passes to
  the hook routine the transit pointer info and the character string
  msg, which contains the message. If the hook routine returns zero,
  the routine print prints the message in an usual way. Otherwise, if
  the hook routine returns non-zero, the message is not printed.

  In order to uninstall the hook routine the parameter hook should be
  specified as NULL (in this case the parameter info is ignored).

  \param[in,out] ls    BCLS problem context.
  \param[in]     info  Transit pointer passed to the hook routine.
  \param[in]     hook  Pointer to the print-hook routine.

*/
void
bcls_set_print_hook( BCLS *ls,
		     void *info,
		     int (*hook)(void *info, char *msg))
{
    ls->print_info = info;
    ls->print_hook = hook;
    return;
}

/*!

  \brief Install a fault-hook routine.

  This routine installs a user-defined fault-hook routine.

  The parameter info is a transit pointer passed to the hook routine.

  The parameter "hook" is an entry point to the user-defined
  fault-hook routine. This routine is called by the routine
  "bcls_fault" every time an error message should be output. The
  routine "bcls_fault" passes to the hook routine the transit pointer
  info and the character string msg, which contains the message. If
  the hook routine returns zero, the routine print prints the message
  in an usual way. Otherwise, if the hook routine returns non-zero,
  the message is not printed.

  In order to uninstall the hook routine the parameter hook should be
  specified as NULL (in this case the parameter info is ignored).

  \param[in,out] ls    BCLS problem context.
  \param[in]     info  Transit pointer passed to the hook routine.
  \param[in]     hook  Pointer to the fault-hook routine.

*/
void
bcls_set_fault_hook( BCLS *ls,
		     void *info,
		     int (*hook)(void *info, char *msg))
{
    ls->fault_info = info;
    ls->fault_hook = hook;
    return;
}

/*!

  \brief Give BCLS access to set of column weights.

  Define a set of column weights.  BCLS will use these to scale the
  steepest-descent steps.

  \param[in,out] ls     BCLS problem context.
  \param[in]     anorm  Array of columns norms of A.

*/
void
bcls_set_anorm( BCLS *ls,
                double anorm[] )
{
    ls->anorm = anorm;
    return;
}

/*!

  \brief Install a user-defined preconditioning routine.

  Replace each subproblem
  \f[
  \def\minimize{\displaystyle\mathop{\hbox{minimize}}}
  \minimize_x \| Ax - b \|
  \f]
  with
  \f[
  \def\minimize{\displaystyle\mathop{\hbox{minimize}}}
  \minimize_y \| AU^{-1}y - b \|
  \quad\mbox{with}\quad
  Ux = y.
  \f]

  \param[in,out]  ls     BCLS problem context.
  \param[in]      Usolve Pointer to the user's preconditioning routine.

*/
void
bcls_set_usolve( BCLS *ls,
                 int (*Usolve)( int mode, int m, int n, int nix,
                                int ix[], double v[], double w[],
                                void *UsrWrk ) )
{
    assert( Usolve != NULL );
    ls->Usolve = Usolve;
    return;
}

/*!

  \brief Compute the column norms of A.

  Compute the columns norms of A.  Each column of A is generated by
  multiplying A by a unit vector.  The two-norm squared of each
  column is stored in aprod.

  This routine must be called *after* bcls_create_prob.  (As with any
  other BCLS routine.)

  \param[in]      ls      BCLS problem context.
  \param[in]      m       Number of rows in A.
  \param[in]      n       Number of columns in A.
  \param[in]      Aprod   User's matrix-product routine.
  \param[in,out]  UsrWrk  Pointer to user's workspace (could be NULL).
  \param[out]     anorm   Vector of column norms of A.

  \return Returns any error codes returned by the user's aprod
  routine.

*/
int
bcls_compute_anorm( BCLS *ls, int n, int m,
                    int (*Aprod)
                    ( int mode, int m, int n, int nix,
                      int ix[], double x[], double y[], void *UsrWrk ),
                    void *UsrWrk,
                    double anorm[] )
{
    // Make sure that the problem instance has been created.
    assert( ls    != NULL );
    assert( anorm != NULL );

    int j;
    int err;
    const int mode = 1;         // Always:  aj = A*ej.
    const int nix  = 1;         // Only one column is ever needed.
    int    *ix = ls->ix;
    double *e  = ls->wrk_u;
    double *aj = ls->wrk_v;

    bcls_dload( n, 1.0, e, 1 );

    for (j = 0; j < n; j++) {

        // Multiply A times the jth unit vector.
        ix[0] = j;

        // aj <- A * ej.
        err = Aprod( mode, m, n, nix, ix, e, aj, UsrWrk );

        // Exit if Aprod returned an error.
        if (err) break;

        // Compute the norm of aj and store it in anorm.
        anorm[j] = cblas_dnrm2( m, aj, 1 );

        // Make sure that the column norm is too small.
        anorm[j] = fmax( BCLS_MIN_COLUMN_NORM, anorm[j] );
    }

    return err;
}

/*!

  \brief Load the problem into the BCLS data structure.

  The routiens loads the complete problem description into a BCLS
  problem instance.  No work is really being done: only the various
  structure elements are being set to point to the right places.

  \param[in,out]  ls      BCLS problem context.
  \param[in]      m       Number of rows in A.  Note: m <= mmax.
  \param[in]      n       Number of columns in A.  Note: n <= nmax.
  \param[in]      Aprod   Hook to user's matrix-vector product routine.
  \param[in,out]  UsrWrk  Transit pointer passed directly to Aprod/Usolve.
  \param[in]      damp    Regularization parameter.
  \param[in,out]  x       The starting point.
  \param[in]      b       The RHS vector.
  \param[in]      c       Defines a linear term (set to NULL if one doesn't exist).
  \param[in]      bl      Lower bounds on x.
  \param[in]      bu      Upper bounds on x.

*/
void
bcls_set_problem_data( BCLS *ls, int m, int n,
		       int (*Aprod)( int mode, int m, int n, int nix,
				     int ix[], double x[], double y[],
				     void *UsrWrk ),
		       void *UsrWrk, double damp,
		       double x[], double b[], double c[],
		       double bl[], double bu[] )
{
    assert( m <= ls->mmax );
    assert( n <= ls->nmax );

    assert( Aprod  != NULL ); ls->Aprod  = Aprod;
    assert( m      >= 0    ); ls->m      = m;
    assert( n      >= 0    ); ls->n      = n;
                              ls->UsrWrk = UsrWrk;
    assert( damp   >= 0.0  ); ls->damp   = damp;
    assert( x      != NULL ); ls->x      = x;
    assert( b      != NULL ); ls->b      = b;
                              ls->c      = c;
    assert( bl     != NULL ); ls->bl     = bl;
    assert( bu     != NULL ); ls->bu     = bu;

    return;
}

/*!

  \brief Return an exit message.

  \param[in] flag  The code of the error.

  \return Error messsage.

*/
char *
bcls_exit_msg( int flag )
{
    char *msg;

    if      (flag==BCLS_EXIT_CNVGD)  msg="Optimal solution found";
    else if (flag==BCLS_EXIT_MAJOR)  msg="Too many major iterations";
    else if (flag==BCLS_EXIT_MINOR)  msg="Too many minor iterations";
    else if (flag==BCLS_EXIT_UNBND)  msg="Found direction of infinite descent";
    else if (flag==BCLS_EXIT_INFEA)  msg="Bounds are inconsistent";
    else if (flag==BCLS_EXIT_APROD)  msg="Aprod requested immediate exit";
    else if (flag==BCLS_EXIT_USOLVE) msg="Usolve requested immediate exit";
    else if (flag==BCLS_EXIT_CALLBK) msg="CallBack requested immediate exit";
    else                             msg="Undefined exit";

    return msg;
}

/*!

  \brief Solve the current problem instance.

  \return
   -  #BCLS_EXIT_CNVGD (0) - Successful exit.  Found an optimal solution.
   -  #BCLS_EXIT_MAJOR     - Too many major iterations.
   -  #BCLS_EXIT_MINOR     - Too many inner iterations.
   -  #BCLS_EXIT_UNBND     - Found direction of infinite descent.
   -  #BCLS_EXIT_INFEA     - Bounds are inconsistent.
   -  #BCLS_EXIT_APROD     - Aprod  requested immediate exit.
   -  #BCLS_EXIT_USOLVE    - Usolve requested immediate exit.

*/
int
bcls_solve_prob( BCLS *ls )
{
    int err;
    int jpInf;
    const int preconditioning = ls->Usolve != NULL;
    double pInf, timeTot;
    double bNorm;  // Norm of the RHS (or 1.0, which ever is bigger).
    BCLS_timer watch;

    // Print the banner.
    //PRINT1(" ----------------------------------------------------\n");
    //PRINT1(" BCLS -- Bound-Constrained Least Squares, Version %s\n",
    //      bcls_version_info() );
    //PRINT1(" Compiled on %s\n",  bcls_compilation_info() );
    //PRINT1(" ----------------------------------------------------\n");

    // -----------------------------------------------------------------
    // Print parameters and diagnostic information.
    // -----------------------------------------------------------------
    bcls_print_params( ls );

    // Examine the bounds and print a summary about them.
    // Also check if the problem is feasible!
    err = bcls_examine_bnds( ls, ls->n, ls->bl, ls->bu );
    if (err) {
	ls->exit = err;
        goto direct_exit;
    }

    // Set the return for longjmp.  First call to setjmp returns 0.
    err = setjmp(ls->jmp_env);
    if (err) {
        ls->exit = err;
        goto direct_exit;
    }

    // Compute and print some statistics about the column scales.
    bcls_examine_column_scales( ls, ls->anorm );

    // Print a log header.
    //    PRINT1("\n %5s  %5s    %9s  %9s  %11s  %9s %7s %7s %7s\n",
    //	   "Major","Minor","Residual","xNorm","Optimal",
    //	   ls->newton_step == BCLS_NEWTON_STEP_LSQR
    //	   ? "LSQR" : "CGLS",
    //	   "nSteps", "Free", "OptFac");

    // -----------------------------------------------------------------
    // Call the BCLS solver.
    // -----------------------------------------------------------------
    bcls_timer( &(ls->stopwatch[BCLS_TIMER_TOTAL]), BCLS_TIMER_START );

    bcls_solver( ls, ls->m, ls->n, &bNorm,
		 ls->x, ls->b, ls->c, ls->bl, ls->bu, ls->r, ls->g,
		 ls->dx, ls->dxFree, ls->ix,
		 ls->aBreak, ls->iBreak, ls->anorm );

    bcls_timer( &(ls->stopwatch[BCLS_TIMER_TOTAL]), BCLS_TIMER_STOP );

    // =================================================================
    // Exits.
    // =================================================================
 direct_exit:
    // -----------------------------------------------------------------
    // Check feasibility and print a solution summary (iterations, etc.)
    // -----------------------------------------------------------------
    bcls_primal_inf( ls->n, ls->x, ls->bl, ls->bu, &pInf, &jpInf );

    //PRINT1("\n");
    //    PRINT1(" BCLS Exit %4d -- %s\n",ls->exit, bcls_exit_msg(ls->exit));
    //PRINT1("\n");
    //PRINT1(" No. of iterations      %8d %7s", ls->itnMin, "");
    //PRINT1(" Objective value       %17.9e\n", ls->soln_obj  );
    //PRINT1(" No. of major iterations%8d %7s", ls->itnMaj, "");
    //PRINT1(" Optimality           (%7d) %8.1e\n",
    //	   ls->soln_jInf,ls->soln_dInf);
    //PRINT1(" No. of calls to Aprod  %8d", ls->nAprodT);

    //if (ls->proj_search == BCLS_PROJ_SEARCH_EXACT)
    //   PRINT1(" (%5d)", ls->nAprod1 );
    //else
    //  PRINT1("  %5s ", "");
    // PRINT1(" Norm of RHS            %16.1e\n", bNorm);
    // if (pInf > ls->eps)
    //  PRINT1(" Feasibility          (%7d) %7.1e\n", jpInf, pInf);
    //if (preconditioning)
    //  PRINT1(" No. of calls to Usolve %8d\n", ls->nUsolve);
    //PRINT1("\n");

    // -----------------------------------------------------------------
    // Print timing statistics.
    // -----------------------------------------------------------------
    //timeTot = fmax(ls->eps,ls->stopwatch[BCLS_TIMER_TOTAL].total);

    //watch   = ls->stopwatch[BCLS_TIMER_TOTAL];
    //timeTot = fmax(1e-6, watch.total); // Safeguard against timeTot = 0.

    //PRINT1(" %-25s %5.1f (%4.2f) secs\n",
    //	   watch.name, watch.total, 1.0 );

    //watch = ls->stopwatch[BCLS_TIMER_LSQR];
    //PRINT1(" %-25s %5.1f (%4.2f) secs\n",
    //	   watch.name, watch.total, watch.total / timeTot );

    //    watch = ls->stopwatch[BCLS_TIMER_APROD];
    //PRINT1(" %-25s %5.1f (%4.2f) secs\n",
    //	   watch.name, watch.total, watch.total / timeTot );

    // Only print time for Usolve is user provided this routine.
    //if (preconditioning) {
      //  watch = ls->stopwatch[BCLS_TIMER_USOLVE];
      //PRINT1(" %-25s %5.1f (%4.2f) secs\n",
      //       watch.name, watch.total, watch.total / timeTot );
    //}

    return (ls->exit + err);
}


#ifndef ARRAY_H
#define ARRAY_H

#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <iostream>
#include <algorithm>
#include <charm++.h>
#if CMK_HAS_CBLAS
#include <cblas.h>
#endif

namespace CharjArray {
  class Range {
  public:
    int size, start, stop;
    Range() {}
    Range(int size_) : size(size_), start(0), stop(size) {}
    Range(int start_, int stop_) :
    size(stop_ - start_), start(start_), stop(stop_) {
      assert(stop >= start);
    }
    void pup(PUP::er& p) {
        p | size;
        p | start;
        p | stop;
    }
  };

  template<int dims>
  class Domain {
  public:
    Range ranges[dims];

    Domain() {}

    Domain(Range ranges_[]) {
      for (int i = 0; i < dims; i++)
	ranges[i] = ranges_[i];
    }

    Domain(Range range) {
      ranges[0] = range;
    }

    Domain(Range range1, Range range2) {
      // TODO: fix Charj generator so it uses the array
      ranges[0] = range1;
      ranges[1] = range2;
    }

    int size() const {
      int total = 0;

      for (int i = 0; i < dims; i++)
	if (total == 0)
	  total = ranges[i].size;
	else
	  total *= ranges[i].size;

      return total;
    }

    void pup(PUP::er& p) {
        for (int i=0; i<dims; ++i) p | ranges[i];
    }
  };

  template<int dims>
  class RowMajor {
  public:
    static int access(const int i, const Domain<dims> &d) {
      return i - d.ranges[0].start;
    }
    static int access(const int i, const int j, const Domain<dims> &d) {
      return (i - d.ranges[0].start) * d.ranges[1].size + j -
        d.ranges[1].start;
    }
    // Generic access method, not used right now.
    // static int access(const int *i, const Domain<dims> &d) {
    //   int off = i[0];
    //   int dimoff = 1;
    //   for (int j = ndims-1; j > 0; --j) {
    //     dimoff *= d.ranges[j].size;
    //     off += dimoff * (i[j] - d.ranges[j].start);
    //   }
    //   return off;
    // }
  };

  template<int dims>
  class ColMajor {
  public:
    static int access(const int i, const Domain<dims> &d) {
      return i - d.ranges[0].start;
    }
    static int access(const int i, const int j, const Domain<dims> &d) {
      return (j - d.ranges[1].start) * d.ranges[1].size + i -
        d.ranges[0].start;
    }
  };

  template<class type, int dims = 1, class atype = RowMajor<dims> >
  class Array {
  private:
    Domain<dims> domain;
    type *block;
    int ref_cout;
    bool did_init;
    Array* ref_parent;

  public:
    Array(Domain<dims> domain_) : ref_parent(0), did_init(false) {
      init(domain_);
    }

    Array(type **block_) : did_init(false) {
      block = *block_;
    }

    Array(type& block_) : did_init(false) {
      block = &block_;
    }

    Array() : ref_parent(0), did_init(false) {

    }

    Array(Array* parent, Domain<dims> domain_)
        : ref_parent(parent), did_init(false) {
      domain = domain_;
      block = parent->block;
    }

    void init(Domain<dims> &domain_) {
      domain = domain_;
      //if (atype == ROW_MAJOR)
      block = new type[domain.size()];
      //printf("Array: allocating memory, size=%d, base pointer=%p\n",
      //       domain.size(), block);
      did_init = true;
    }

    type* raw() { return block; }

    ~Array() {
      if (did_init) delete[] block;
    }

    /*type* operator[] (const Domain<dims> &domain) {
      return block[domain.ranges[0].size];
      }*/

    type& operator[] (const int i) {
      return block[atype::access(i, domain)];
    }

    const type& operator[] (const int i) const {
      return block[atype::access(i, domain)];
    }

    type& access(const int i) {
      return this->operator[](i);
    }

    type& access(const int i, const int j) {
      //printf("Array: accessing, index (%d,%d), offset=%d, base pointer=%p\n",
      //i, j, atype::access(i, j, domain), block);
      return block[atype::access(i, j, domain)];
    }

    type& access(const int i, const Range r) {
      Domain<1> d(r);
      //printf("Array: accessing subrange, size = %d, range (%d,%d), base pointer=%p\n",
      //d.size(), r.start, r.stop, block);
      type* buf = new type[d.size()];
      for (int j = 0; j < d.size(); j++) {
        //printf("Array: copying element (%d,%d), base pointer=%p\n", i, j, block);
        buf[j] = block[atype::access(i, j, domain)];
      }
      return *buf;
    }

    const type& access(const int i, const int j) const {
      return block[atype::access(i, j, domain)];
    }

    Array<type, dims, atype>* operator[] (const Domain<dims> &domain) {
      return new Array<type, dims, atype>(this, domain);
    }

    int size() const {
      return domain.size();
    }

    int size(int dim) const {
      return domain.ranges[dim].size;
    }

    void pup(PUP::er& p) {
        p | domain;
        if (p.isUnpacking()) {
            block = new type[domain.size()];
        }
        PUParray(p, block, domain.size());
    }

    void fill(const type &t) {
      for (int i = 0; i < domain.size(); ++i)
	block[i] = t;
    }

    /// Do these arrays have the same shape and contents?
    bool operator==(const Array &rhs) const
    {
      for (int i = 0; i < dims; ++i)
	if (this->size(i) != rhs.size(i))
	  return false;

      for (int i = 0; i < this->size(); ++i)
	if (this->block[i] != rhs.block[i])
	  return false;

      return true;
    }
    bool operator!=(const Array &rhs) const
    {
      return !(*this == rhs);
    }
  };

  /**
     A local Matrix class for various sorts of linear-algebra work.

     Indexed from 0, to reflect the C-heritage of Charj.
   */
  template <typename V, class atype = RowMajor<2> >
  class Matrix : public Array<V, 2, atype>
  {
  public:
    Matrix() { }
    /// A square matrix
    Matrix(unsigned int n) : Array<V,2,atype>(Domain<2>(n,n)) { }

    /// A identity matrix
    static Matrix* ident(int n)
    {
      Matrix *ret = new Matrix(n);
      ret->fill(0);

      for (int i = 0; i < n; ++i)
	ret->access(i,i) = 1;

      return ret;
    }
  };

  template <typename T, class atype = RowMajor<1> >
  class Vector : public Array<T, 1, atype>
  {
  public:
    Vector() { }
    Vector(unsigned int n) : Array<T, 1, atype>(Range(n)) { }
  };

  /// Compute the inner (dot) product v1^T * v2
  // To compute v1^H * v2, call as dot(v1.C(), v2)
  template<typename T, class atype1, class atype2>
  T dot(const Vector<T, atype1> *pv1, const Vector<T, atype2> *pv2)
  {
    const Vector<T, atype1> &v1 = *pv1, &v2 = *pv2;
    assert(v1.size() == v2.size());
    // XXX: This default initialization worries me some, since it
    // won't necessarily be an additive identity for all T. - Phil
    T ret = T();
    int size = v1.size();
    for (int i = 0; i < size; ++i)
      ret += v1[i] * v2[i];
    return ret;
  }
#if CMK_HAS_CBLAS
  template <>
  float dot<float, RowMajor<1>, RowMajor<1> >(const Vector<float, RowMajor<1> > *pv1,
					      const Vector<float, RowMajor<1> > *pv2)
  {
    const Vector<float, RowMajor<1> > &v1 = *pv1, &v2 = *pv2;
    assert(v1.size() == v2.size());
    return cblas_sdot(v1.size(), &(v1[0]), 1, &(v2[0]), 1);
  }
  template <>
  double dot<double, RowMajor<1>, RowMajor<1> >(const Vector<double, RowMajor<1> > *pv1,
						const Vector<double, RowMajor<1> > *pv2)
  {
    const Vector<double, RowMajor<1> > &v1 = *pv1, &v2 = *pv2;
    assert(v1.size() == v2.size());
    return cblas_ddot(v1.size(), &(v1[0]), 1, &(v2[0]), 1);
  }
#endif

  /// Computer the 1-norm of the given vector
  template<typename T, class atype>
    T norm1(const Vector<T, atype> *pv)
  {
    const Vector<T, atype> &v = *pv;
    // XXX: See comment about additive identity in dot(), above
    T ret = T();
    int size = v.size();
    for (int i = 0; i < size; ++i)
      ret += v[i];
    return ret;
  }

  /// Compute the Euclidean (2) norm of the given vector
  template<typename T, class atype>
    T norm2(const Vector<T, atype> *pv)
  {
    const Vector<T, atype> &v = *pv;
    // XXX: See comment about additive identity in dot(), above
    T ret = T();
    int size = v.size();
    for (int i = 0; i < size; ++i)
      ret += v[i] * v[i];
    return sqrt(ret);
  }
#if CMK_HAS_CBLAS
  template<>
    float norm2<float, RowMajor<1> >(const Vector<float, RowMajor<1> > *pv)
  {
    const Vector<float, RowMajor<1> > &v = *pv;
    return cblas_snrm2(v.size(), &(v[0]), 1);
  }
  template<>
    double norm2<double, RowMajor<1> >(const Vector<double, RowMajor<1> > *pv)
  {
    const Vector<double, RowMajor<1> > &v = *pv;
    return cblas_dnrm2(v.size(), &(v[0]), 1);
  }
#endif

  /// Compute the infinity (max) norm of the given vector
  // Will fail on zero-length vectors
  template<typename T, class atype>
    T normI(const Vector<T, atype> *pv)
  {
    const Vector<T, atype> &v = *pv;
    T ret = v[0];
    int size = v.size();
    for (int i = 1; i < size; ++i)
      ret = max(ret, v[i]);
    return ret;
  }

  /// Scale a vector by some constant
  template<typename T, typename U, class atype>
    void scale(const T &t, Vector<U, atype> *pv)
  {
    const Vector<T, atype> &v = *pv;
    int size = v.size();
    for (int i = 0; i < size; ++i)
      v[i] = t * v[i];
  }
#if CMK_HAS_CBLAS
  template<>
    void scale<float, float, RowMajor<1> >(const float &t,
					   Vector<float, RowMajor<1> > *pv)
  {
    Vector<float, RowMajor<1> > &v = *pv;
    cblas_sscal(v.size(), t, &(v[0]), 1);
  }
  template<>
    void scale<double, double, RowMajor<1> >(const double &t,
					     Vector<double, RowMajor<1> > *pv)
  {
    Vector<double, RowMajor<1> > &v = *pv;
    cblas_dscal(v.size(), t, &(v[0]), 1);
  }
#endif

  /// Add one vector to a scaled version of another
  template<typename T, typename U, class atype>
    void axpy(const T &a, const Vector<U, atype> *px, Vector<U, atype> *py)
  {
    Vector<T, atype> &x = *px;
    const Vector<T, atype> &y = *py;
    int size = x.size();
    assert(size == y.size());
    for (int i = 0; i < size; ++i)
      x[i] = a * x[i] + y[i];
  }
#if CMK_HAS_CBLAS
  template<>
    void axpy<float, float, RowMajor<1> >(const float &a,
					  const Vector<float, RowMajor<1> > *px,
					  Vector<float, RowMajor<1> > *py)
  {
    const Vector<float, RowMajor<1> > &x = *px;
    Vector<float, RowMajor<1> > &y = *py;
    int size = x.size();
    assert(size == y.size());
    cblas_saxpy(size, a, &(x[0]), 1, &(y[0]), 1);
  }
  template<>
    void axpy<double, double, RowMajor<1> >(const double &a,
					  const Vector<double, RowMajor<1> > *px,
					  Vector<double, RowMajor<1> > *py)
  {
    const Vector<double, RowMajor<1> > &x = *px;
    Vector<double, RowMajor<1> > &y = *py;
    int size = x.size();
    assert(size == y.size());
    cblas_daxpy(size, a, &(x[0]), 1, &(y[0]), 1);
  }
#endif
}

#endif


// 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;
  }
}


\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}


/* $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 */


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright 2018 - Matteo Ragni, Matteo Cocetti - University of Trento
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#ifndef LIBNEWTON_H_
#define LIBNEWTON_H_

#include <lapacke.h>
#include <cblas.h>

/**
 * @brief Jacobian Callback for the Newton algorithm
 *
 * The callback receivs the current input, the current control, and the list of
 * parameter vectors. It should store the first argument the Jacobian matrix (stored as an array).
 * To select the ordering in the matrix, you must select the correct ordering in newton_options
 * structure, at the ordering input.
 * @param df output vector (vectorized Jacobian matrix)
 * @param t current time for evaluation
 * @param x current point for evaluation
 * @param u current control for evaluation
 * @param p array of parameter vectors
 * @param data user space input (simply use it as a pointer casted to void)
 */
typedef void (*newton_jacobian)(
    double *df,
    const double t,
    const double *x,
    const double *u,
    const double **p,
    void *data);

/**
 * @brief Vector Field Callback for the Newton algorithm
 *
 * The callback receivs the current input, the current control, and the list of
 * parameter vectors. It should store the first argument the function output vector.
 * @param df output vector (vectorized Jacobian matrix)
 * @param t current time for evaluation
 * @param x current point for evaluation
 * @param u current control for evaluation
 * @param p array of parameter vectors
 * @param data user space input (simply use it as a pointer casted to void)
 */
typedef void (*newton_function)(
    double *f,
    const double t,
    const double *x,
    const double *u,
    const double **p,
    void *data);

/**
 * @brief Options for the Newton Algorithm
 *
 * This structure contains all the options for the Newton algorithm, alongside the callbacks.
 * The structure will be modified by the algorithm with some debug information, such as number
 * of iterations, tolerances and ordering for the jacobian matrix.
 */
typedef struct newton_options {
  lapack_int ordering; /**< Should be LAPACK_ROW_MAJOR or LAPACK_COL_MAJOR */
  lapack_int f_size;   /**< Vector field size */
  lapack_int x_size;   /**< Variable vector size */
  double f_tol;        /**< Stopping tolerance for the zero.
                              At the end will contain the 2 norm of the
                              vector field in the solution */
  double x_tol;        /**< Stopping tolerance for the x vector step.
                              At the end will contain the 2 norm of the last update step */
  lapack_int max_iter; /**< Maximum number of iteration,
                              At the end will contain the number of step executed  */
  newton_function f;   /**< Pointer to vector field callback */
  newton_jacobian df;  /**<  Pointer to Jacobian callback */
} newton_options;

/**
 * @brief Error code returned by the algorithm
 */
typedef enum newton_ret {
  NEWTON_F_TOL = 0,         /**< (0) The solver reached the required tolerance limit for the vector field */
  NEWTON_X_TOL,             /**< (1) The last step for solution update was less than the minimum */
  NEWTON_MAX_ITER,          /**< (2) Maximum number of iterations reached */
  NEWTON_SINGULAR_JACOBIAN, /**< (3 LAPACKE) The jacobian is singular */
  NEWTON_ILLEGAL_JACOBIAN,  /**< (4 LAPACKE) Illegal jacobian */
  NEWTON_MALLOC_ERROR,      /**< (5) Cannot allocate memory */
  NEWTON_GENERIC_ERROR      /**< (6) Generic error in the execution of the algorithm */
} newton_ret;

/**
 * @brief Boolean implementation
 */
typedef enum newton_bool {
  NEWTON_FALSE = 0, /**< False */
  NEWTON_TRUE       /**< True */
} newton_bool;

/**
 * @brief Executes the Newton algorithm for root finding
 *
 * Executes the Newton algorithm for root finding. The step Performed is:
 * \f{
 *   x_{k+1} - x_{k} = -\nabla F^{-1}(x_k, u, p) F(x_k, u, p)
 * \f}
 * and the solution is found by using DGELS defined in LAPACK library.
 * The stopping conditions are:
 *  * Number of iterations bigger than maximum allowed (specified in newton_options)
 *  * \f$ |x_{k+1} - x_k| \leq x_{tol}\f$
 *  * \f$ |f(x_k, u, p)| \leq y_{tol}\f$
 * and the function and jacobian are evaluated through callbacks. On exit, the values
 * inside the option structure are update for debuggin purposes (this is why Newton
 * options is not a const pointer). It returns a status enum.
 * @param opt option structure
 * @param x root position and initial condition. Will be modified
 * @param u control action input. It can be NULL.
 * @param p parameter array of vectors. It can be NULL.
 * @param data space for user data. Will be passed to callbacks. It can be NULL
 * @return a status exit code as described in newton_ret enum.
 */
newton_ret newton_solve(
    newton_options *opt,
    const double t,
    double *x,
    const double *u,
    const double **p,
    void *data);

#endif