/*
 * -----------------------------------------------------------------
 * $Revision: 855 $
 * $Date: 2005-02-09 18:15:46 -0500 (Wed, 09 Feb 2005) $
 * ----------------------------------------------------------------- 
 * Programmer(s): Scott D. Cohen, Alan C. Hindmarsh, Radu Serban,
 *                and Aaron Collier @ LLNL
 * -----------------------------------------------------------------
 * Copyright (c) 2002, The Regents of the University of California.
 * Produced at the Lawrence Livermore National Laboratory.
 * All rights reserved.
 * For details, see sundials/shared/LICENSE.
 * -----------------------------------------------------------------
 * This is the implementation file for a nrnthread implementation
 * of the NVECTOR package.
 * -----------------------------------------------------------------
 */

#define USELONGDOUBLE 1

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

#include "shared/nvector_serial.h"
#include "nvector_nrnthread_ld.h"
#include "shared/sundialsmath.h"
#include "shared/sundialstypes.h"
#include "section.h"
#include "nrnmutdec.h"

#define ZERO   RCONST(0.0)
#define HALF   RCONST(0.5)
#define ONE    RCONST(1.0)
#define ONEPT5 RCONST(1.5)

#if 0
#define mydebug(a) printf(a)
#define mydebug2(a,b) printf(a,b)
#else
#define mydebug(a) /**/
#define mydebug2(a,b) /**/
#endif

#if USELONGDOUBLE
#define ldrealtype long double
#else
#define ldrealtype realtype
#endif

#if USE_PTHREAD
static MUTDEC
#endif
/* argument passing between NrnThreadLD and Serial */
static N_Vector x_;
static N_Vector y_;
static N_Vector z_;
static N_Vector w_;
static N_Vector id_;
static realtype a_;
static realtype b_;
static realtype c_;
static realtype retval;
#if USELONGDOUBLE
static long double longdretval;
#endif
static booleantype bretval;
#define xpass x_ = x;
#define ypass y_ = y;
#define zpass z_ = z;
#define wpass w_ = w;
#define idpass id_ = id;
#define apass a_ = a;
#define bpass b_ = b;
#define cpass c_ = c;
#define xarg(i) NV_SUBVEC_NT_LD(x_, i)
#define yarg(i) NV_SUBVEC_NT_LD(y_, i)
#define zarg(i) NV_SUBVEC_NT_LD(z_, i)
#define warg(i) NV_SUBVEC_NT_LD(w_, i)
#define idarg(i) NV_SUBVEC_NT_LD(id_, i)
#define aarg a_
#define barg b_
#define carg c_
#define lock MUTLOCK
#define unlock MUTUNLOCK
#define lockadd(arg) lock; retval += arg; unlock;
#if USELONGDOUBLE
#define locklongdadd(arg) lock; longdretval += arg; unlock;
#else
#define locklongdadd(arg) lockadd(arg)
#endif
#define lockmax(arg) lock; if (retval < arg) { retval = arg; }; unlock;
#define lockmin(arg) lock; if (retval > arg) { retval = arg; }; unlock;
#define lockfalse lock; bretval = FALSE; unlock;

/*
 * -----------------------------------------------------------------
 * exported functions
 * -----------------------------------------------------------------
 */

/* ----------------------------------------------------------------------------
 * Function to create a new empty nrnthread vector 
 */

N_Vector N_VNewEmpty_NrnThreadLD(long int length, int nthread, long int* sizes)
{
  int i;
  N_Vector v;
  N_Vector_Ops ops;
  N_VectorContent_NrnThreadLD content;

  if (!MUTCONSTRUCTED) { MUTCONSTRUCT(1) }

  /* Create vector */
  v = (N_Vector) malloc(sizeof *v);
  if (v == NULL) return(NULL);
  
  /* Create vector operation structure */
  ops = (N_Vector_Ops) malloc(sizeof(struct _generic_N_Vector_Ops));
  if (ops == NULL) {free(v);return(NULL);}

  ops->nvclone           = N_VClone_NrnThreadLD;
  ops->nvdestroy         = N_VDestroy_NrnThreadLD;
  ops->nvspace           = N_VSpace_NrnThreadLD;
  ops->nvgetarraypointer = N_VGetArrayPointer_NrnThreadLD;
  ops->nvsetarraypointer = N_VSetArrayPointer_NrnThreadLD;
  ops->nvlinearsum       = N_VLinearSum_NrnThreadLD;
  ops->nvconst           = N_VConst_NrnThreadLD;
  ops->nvprod            = N_VProd_NrnThreadLD;
  ops->nvdiv             = N_VDiv_NrnThreadLD;
  ops->nvscale           = N_VScale_NrnThreadLD;
  ops->nvabs             = N_VAbs_NrnThreadLD;
  ops->nvinv             = N_VInv_NrnThreadLD;
  ops->nvaddconst        = N_VAddConst_NrnThreadLD;
  ops->nvdotprod         = N_VDotProd_NrnThreadLD;
  ops->nvmaxnorm         = N_VMaxNorm_NrnThreadLD;
  ops->nvwrmsnormmask    = N_VWrmsNormMask_NrnThreadLD;
  ops->nvwrmsnorm        = N_VWrmsNorm_NrnThreadLD;
  ops->nvmin             = N_VMin_NrnThreadLD;
  ops->nvwl2norm         = N_VWL2Norm_NrnThreadLD;
  ops->nvl1norm          = N_VL1Norm_NrnThreadLD;
  ops->nvcompare         = N_VCompare_NrnThreadLD;
  ops->nvinvtest         = N_VInvTest_NrnThreadLD;
  ops->nvconstrmask      = N_VConstrMask_NrnThreadLD;
  ops->nvminquotient     = N_VMinQuotient_NrnThreadLD;

  /* Create content */
  content = (N_VectorContent_NrnThreadLD) malloc(sizeof(struct _N_VectorContent_NrnThreadLD));
  if (content == NULL) {free(ops);free(v);return(NULL);}

  content->length = length;
  content->nt = nthread;
  content->own_data = FALSE;
  content->data = (N_Vector*) malloc(sizeof(N_Vector)*nthread);
  if (content->data == NULL) { free(ops); free(v); free(content); return(NULL);}
  for (i=0; i < nthread; ++i) {
    content->data[i] = NULL;
  }
  /* Attach content and ops */
  v->content = content;
  v->ops = ops;

  return(v);
}

/* ----------------------------------------------------------------------------
 * Function to create a new nrnthread vector 
 */

N_Vector N_VNew_NrnThreadLD(long int length, int nthread, long int* sizes)
{
  int i;
  N_Vector v;
  N_Vector data;
  N_VectorContent_NrnThreadLD* content;

  v = N_VNewEmpty_NrnThreadLD(length, nthread, sizes);
  if (v == NULL) return(NULL);

  /* Create data */
  if (length > 0) {

    /* Allocate memory */
    NV_OWN_DATA_NT_LD(v) = TRUE;
    for (i=0; i < nthread; ++i) {
    	data = N_VNew_Serial(sizes[i]);
        if(data == NULL) {N_VDestroy_NrnThreadLD(v);return(NULL);}
        NV_SUBVEC_NT_LD(v, i) = data;
    }
  }

  return(v);
}

/* ----------------------------------------------------------------------------
 * Function to clone from a template a new vector with empty (NULL) data array
 */

N_Vector N_VCloneEmpty_NrnThreadLD(N_Vector w)
{
  int i;
  N_Vector v;
  N_Vector_Ops ops;
  N_VectorContent_NrnThreadLD content;
  N_VectorContent_NrnThreadLD wcontent;

  if (w == NULL) return(NULL);

  /* Create vector */
  v = (N_Vector) malloc(sizeof *v);
  if (v == NULL) return(NULL);

  /* Create vector operation structure */
  ops = (N_Vector_Ops) malloc(sizeof(struct _generic_N_Vector_Ops));
  if (ops == NULL) {free(v);return(NULL);}
  
  ops->nvclone           = w->ops->nvclone;
  ops->nvdestroy         = w->ops->nvdestroy;
  ops->nvspace           = w->ops->nvspace;
  ops->nvgetarraypointer = w->ops->nvgetarraypointer;
  ops->nvsetarraypointer = w->ops->nvsetarraypointer;
  ops->nvlinearsum       = w->ops->nvlinearsum;
  ops->nvconst           = w->ops->nvconst;  
  ops->nvprod            = w->ops->nvprod;   
  ops->nvdiv             = w->ops->nvdiv;
  ops->nvscale           = w->ops->nvscale; 
  ops->nvabs             = w->ops->nvabs;
  ops->nvinv             = w->ops->nvinv;
  ops->nvaddconst        = w->ops->nvaddconst;
  ops->nvdotprod         = w->ops->nvdotprod;
  ops->nvmaxnorm         = w->ops->nvmaxnorm;
  ops->nvwrmsnormmask    = w->ops->nvwrmsnormmask;
  ops->nvwrmsnorm        = w->ops->nvwrmsnorm;
  ops->nvmin             = w->ops->nvmin;
  ops->nvwl2norm         = w->ops->nvwl2norm;
  ops->nvl1norm          = w->ops->nvl1norm;
  ops->nvcompare         = w->ops->nvcompare;    
  ops->nvinvtest         = w->ops->nvinvtest;
  ops->nvconstrmask      = w->ops->nvconstrmask;
  ops->nvminquotient     = w->ops->nvminquotient;

  /* Create content */
  content = (N_VectorContent_NrnThreadLD) malloc(sizeof(struct _N_VectorContent_NrnThreadLD));
  if (content == NULL) {free(ops);free(v);return(NULL);}

  wcontent = NV_CONTENT_NT_LD(w);
  content->length = NV_LENGTH_NT_LD(w);
  content->own_data = FALSE;
  content->nt = wcontent->nt;
  content->data = (N_Vector*) malloc(sizeof(N_Vector) * content->nt);
  if (content->data == NULL) { free(ops); free(v); free(content); return(NULL);}
  for (i=0; i < content->nt; ++i) {
    content->data[i] = NULL;
  }

  /* Attach content and ops */
  v->content = content;
  v->ops = ops;

  return(v);
}

/* ----------------------------------------------------------------------------
 * Function to create a nrnthread N_Vector with user data component 
 */

N_Vector N_VMake_NrnThreadLD(long int length, realtype *v_data)
{
  N_Vector v = NULL;

  assert(0);
#if 0
  v = N_VNewEmpty_NrnThreadLD(length);
  if (v == NULL) return(NULL);

  if (length > 0) {
    /* Attach data */
    NV_OWN_DATA_NT_LD(v) = FALSE;
    NV_DATA_NT_LD(v) = v_data;
  }
#endif
  return(v);
}

/* ----------------------------------------------------------------------------
 * Function to create an array of new nrnthread vectors. 
 */

N_Vector *N_VNewVectorArray_NrnThreadLD(int count, long int length, int nthread, long int* sizes)
{
  N_Vector *vs;
  int j;

  if (count <= 0) return(NULL);

  vs = (N_Vector *) malloc(count * sizeof(N_Vector));
  if(vs == NULL) return(NULL);

  for (j=0; j<count; j++) {
    vs[j] = N_VNew_NrnThreadLD(length, nthread, sizes);
    if (vs[j] == NULL) {
      N_VDestroyVectorArray_NrnThreadLD(vs, j-1);
      return(NULL);
    }
  }

  return(vs);
}

/* ----------------------------------------------------------------------------
 * Function to create an array of new nrnthread vectors with NULL data array. 
 */

N_Vector *N_VNewVectorArrayEmpty_NrnThreadLD(int count, long int length, int nthread, long int* sizes)
{
  N_Vector *vs;
  int j;

  if (count <= 0) return(NULL);

  vs = (N_Vector *) malloc(count * sizeof(N_Vector));
  if(vs == NULL) return(NULL);

  for (j=0; j<count; j++) {
    vs[j] = N_VNewEmpty_NrnThreadLD(length, nthread, sizes);
    if (vs[j] == NULL) {
      N_VDestroyVectorArray_NrnThreadLD(vs, j-1);
      return(NULL);
    }
  }

  return(vs);
}

/* ----------------------------------------------------------------------------
 * Function to free an array created with N_VNewVectorArray_NrnThreadLD
 */

void N_VDestroyVectorArray_NrnThreadLD(N_Vector *vs, int count)
{
  int j;

  for (j = 0; j < count; j++) N_VDestroy_NrnThreadLD(vs[j]);

  free(vs);
}

/* ----------------------------------------------------------------------------
 * Function to print the a nrnthread vector 
 */
 
void N_VPrint_NrnThreadLD(N_Vector x)
{
  int i;
  int nt;

  nt  = NV_NT_NT_LD(x);

  for (i=0; i < nt; i++) {
    N_VPrint_Serial(NV_SUBVEC_NT_LD(x, i));
  }
  printf("\n");
}

static void pr(N_Vector x) { N_VPrint_NrnThreadLD(x); }
/*
 * -----------------------------------------------------------------
 * implementation of vector operations
 * -----------------------------------------------------------------
 */

N_Vector N_VClone_NrnThreadLD(N_Vector w)
{
  N_Vector v;
  N_Vector wdata;
  N_Vector data;
  long int length;
  int i, nt;

  v = N_VCloneEmpty_NrnThreadLD(w);
  if (v == NULL) return(NULL);

  length = NV_LENGTH_NT_LD(w);
  nt = NV_NT_NT_LD(w);

  /* Create data */
  if (length > 0) {
    NV_OWN_DATA_NT_LD(v) = TRUE;
    for (i=0; i < nt; ++i) {
      wdata = NV_SUBVEC_NT_LD(w, i);
      data = N_VClone(wdata);
      if(data == NULL) {N_VDestroy_NrnThreadLD(v);return(NULL);}
      NV_SUBVEC_NT_LD(v, i) = data;
    }
    /* Attach data */
  
  }

  return(v);
}

void N_VDestroy_NrnThreadLD(N_Vector v)
{
  int i, nt;
  N_Vector data;
  nt = NV_NT_NT_LD(v);
  if (NV_OWN_DATA_NT_LD(v) == TRUE) {
    if (NV_CONTENT_NT_LD(v)->data) {
      for (i = 0; i < nt; ++i) {
        data = NV_SUBVEC_NT_LD(v, i);
        if (data) { N_VDestroy(data); }
      }
      free(NV_CONTENT_NT_LD(v)->data);
    }
  }
  free(v->content);
  free(v->ops);
  free(v);
}

void N_VSpace_NrnThreadLD(N_Vector v, long int *lrw, long int *liw)
{
  *lrw = NV_LENGTH_NT_LD(v);
  *liw = 1;
}

/* NOTICE: the pointer returned is actually the NVector* data where
data is nthread NVector. so when you get the realtype* cast it back to
(NVector*)
*/
realtype *N_VGetArrayPointer_NrnThreadLD(N_Vector v)
{
  N_Vector *v_data;
  v_data = NV_DATA_NT_LD(v);

  return((realtype*)v_data);
}

void N_VSetArrayPointer_NrnThreadLD(realtype *v_data, N_Vector v)
{
  assert(0);
#if 0
  if (NV_LENGTH_NT_LD(v) > 0) NV_DATA_NT_LD(v) = v_data;
#endif
}

static void* vlinearsum(NrnThread* nt) {
  int i = nt->id;
  N_VLinearSum_Serial(aarg, xarg(i), barg, yarg(i), zarg(i));
  return (void*)0;
}
void N_VLinearSum_NrnThreadLD(realtype a, N_Vector x, realtype b, N_Vector y, N_Vector z)
{
  apass bpass xpass ypass zpass
  nrn_multithread_job(vlinearsum);
mydebug("vlinearsum\n");
/*pr(z);*/
}

static void* vconst(NrnThread* nt) {
  int i = nt->id;
  N_VConst_Serial(carg, zarg(i));
  return (void*)0;
}
void N_VConst_NrnThreadLD(realtype c, N_Vector z)
{
  cpass zpass
  nrn_multithread_job(vconst);
mydebug("vconst\n");
}

static void* vprod(NrnThread* nt) {
  int i = nt->id;
  N_VProd_Serial(xarg(i), yarg(i), zarg(i));
  return (void*)0;
}
void N_VProd_NrnThreadLD(N_Vector x, N_Vector y, N_Vector z)
{
  xpass ypass zpass
  nrn_multithread_job(vprod);
mydebug("vprod\n");
}

static void* vdiv(NrnThread* nt) {
  int i = nt->id;
  N_VDiv_Serial(xarg(i), yarg(i), zarg(i));
  return (void*)0;
}
void N_VDiv_NrnThreadLD(N_Vector x, N_Vector y, N_Vector z)
{
  xpass ypass zpass
  nrn_multithread_job(vdiv);
mydebug("vdiv\n");
}

static void* vscale(NrnThread* nt) {
  int i = nt->id;
  N_VScale_Serial(carg, xarg(i), zarg(i));
  return (void*)0;
}
void N_VScale_NrnThreadLD(realtype c, N_Vector x, N_Vector z)
{
  cpass xpass zpass
  nrn_multithread_job(vscale);
mydebug("vscale\n");
/*pr(z);*/
}

static void* vabs(NrnThread* nt) {
  int i = nt->id;
  N_VAbs_Serial(xarg(i), zarg(i));
  return (void*)0;
}
void N_VAbs_NrnThreadLD(N_Vector x, N_Vector z)
{
  xpass zpass
  nrn_multithread_job(vabs);
mydebug("vabs\n");
}

static void* vinv(NrnThread* nt) {
  int i = nt->id;
  N_VInv_Serial(xarg(i), zarg(i));
  return (void*)0;
}
void N_VInv_NrnThreadLD(N_Vector x, N_Vector z)
{
  xpass zpass
  nrn_multithread_job(vinv);
mydebug("vinv\n");
}

static void* vaddconst(NrnThread* nt) {
  int i = nt->id;
  N_VAddConst_Serial(xarg(i), barg, zarg(i));
  return (void*)0;
}
void N_VAddConst_NrnThreadLD(N_Vector x, realtype b, N_Vector z)
{
  bpass xpass zpass
  nrn_multithread_job(vaddconst);
mydebug("vaddconst\n");
}

static void* vdotprod(NrnThread* nt) {
  realtype s;
  int i = nt->id;
  s = N_VDotProd_Serial(xarg(i), yarg(i));
  lockadd(s);
  return (void*)0;
}
realtype N_VDotProd_NrnThreadLD(N_Vector x, N_Vector y)
{
  retval = ZERO;
  xpass ypass
  nrn_multithread_job(vdotprod);
mydebug2("vdotprod %.20g\n", retval);
  return(retval);
}

static void* vmaxnorm(NrnThread* nt) {
  realtype max;
  int i = nt->id;
  max = N_VMaxNorm_Serial(xarg(i));
  lockmax(max);
  return (void*)0;
}
realtype N_VMaxNorm_NrnThreadLD(N_Vector x)
{
  retval = ZERO;
  xpass
  nrn_multithread_job(vmaxnorm);
mydebug2("vmaxnorm %.20g\n", retval);
  return(retval);
}


static ldrealtype vwrmsnorm_help(N_Vector x, N_Vector w)
{
  long int i, N;
  ldrealtype sum = ZERO;
  realtype prodi, *xd, *wd;

  N  = NV_LENGTH_S(x);
  xd = NV_DATA_S(x);
  wd = NV_DATA_S(w);

  for (i=0; i < N; i++) {
    prodi = (*xd++) * (*wd++);
    sum += prodi * prodi;
  }

  return(sum);
}
static void* vwrmsnorm(NrnThread* nt) {
  ldrealtype s;
  int i = nt->id;
  s = vwrmsnorm_help(xarg(i), warg(i));
  locklongdadd(s);
  return (void*)0;
}
realtype N_VWrmsNorm_NrnThreadLD(N_Vector x, N_Vector w)
{
  long int N;
  N  = NV_LENGTH_NT_LD(x);
#if USELONGDOUBLE
  longdretval = ZERO;
#else
  retval = ZERO;
#endif
  xpass  wpass
  nrn_multithread_job(vwrmsnorm);
#if USELONGDOUBLE
  retval = longdretval;
#endif
mydebug2("vwrmsnorm %.20g\n", RSqrt(retval / N));
  return(RSqrt(retval / N));
}

static realtype vwrmsnormmask_help(N_Vector x, N_Vector w, N_Vector id)
{
  long int i, N;
  realtype sum = ZERO, prodi, *xd, *wd, *idd;

  N  = NV_LENGTH_S(x);
  xd  = NV_DATA_S(x);
  wd  = NV_DATA_S(w);
  idd = NV_DATA_S(id);

  for (i=0; i < N; i++) {
    if (idd[i] > ZERO) {
      prodi = xd[i] * wd[i];
      sum += prodi * prodi;
    }
  }

  return(sum);
}
static void* vwrmsnormmask(NrnThread* nt) {
  realtype s;
  int i = nt->id;
  s = vwrmsnormmask_help(xarg(i), warg(i), idarg(i));
  lockadd(s);
  return (void*)0;
}
realtype N_VWrmsNormMask_NrnThreadLD(N_Vector x, N_Vector w, N_Vector id)
{
  long int N;
  N  = NV_LENGTH_NT_LD(x);
  retval = ZERO;
  xpass wpass idpass
  nrn_multithread_job(vwrmsnormmask);
mydebug2("vwrmsnormmask %.20g\n", RSqrt(retval / N));
  return(RSqrt(retval / N));
}

static void* vmin(NrnThread* nt) {
  realtype min;
  int i = nt->id;
  if (NV_LENGTH_S(xarg(i))) {
	  min = N_VMin_Serial(xarg(i));
	  lockmin(min);
}
  return (void*)0;
}
realtype N_VMin_NrnThreadLD(N_Vector x)
{
  retval = BIG_REAL;
  xpass
  nrn_multithread_job(vmin);
mydebug2("vmin %.20g\n", retval);
  return(retval);
}

static realtype N_VWL2Norm_helper(N_Vector x, N_Vector w)
{
  long int i, N;
  realtype sum = ZERO, prodi, *xd, *wd;

  N  = NV_LENGTH_S(x);
  xd = NV_DATA_S(x);
  wd = NV_DATA_S(w);

  for (i=0; i < N; i++) {
    prodi = (*xd++) * (*wd++);
    sum += prodi * prodi;
  }

  return sum;
}
static void* vwl2norm(NrnThread* nt) {
  realtype sum;
  int i = nt->id;
  sum = N_VWL2Norm_helper(xarg(i), warg(i));
  lockadd(sum);
  return (void*)0;
}
realtype N_VWL2Norm_NrnThreadLD(N_Vector x, N_Vector w)
{
  long int N;
  retval = ZERO;
  xpass wpass
  nrn_multithread_job(vwl2norm);
  N  = NV_LENGTH_NT_LD(x);
mydebug2("vwl2norm %.20g\n", RSqrt(retval));
  return(RSqrt(retval));
}

static void* vl1norm(NrnThread* nt) {
  realtype sum;
  int i = nt->id;
  sum = N_VL1Norm_Serial(xarg(i));
  lockadd(sum);
  return (void*)0;
}
realtype N_VL1Norm_NrnThreadLD(N_Vector x)
{
  retval = ZERO;
  xpass
  nrn_multithread_job(vl1norm);
mydebug2("vl1norm %.20g\n", retval);
  return(retval);
}

static void* v1mask(NrnThread* nt) {
  int i = nt->id;
  N_VOneMask_Serial(xarg(i));
  return (void*)0;
}
void N_VOneMask_NrnThreadLD(N_Vector x)
{
  xpass
  nrn_multithread_job(v1mask);
}

static void* vcompare(NrnThread* nt) {
  int i = nt->id;
  N_VCompare_Serial(carg, xarg(i), zarg(i));
  return (void*)0;
}
void N_VCompare_NrnThreadLD(realtype c, N_Vector x, N_Vector z)
{
  cpass xpass zpass
  nrn_multithread_job(vcompare);
mydebug("vcompare\n");
}

static void* vinvtest(NrnThread* nt) {
  booleantype b;
  int i = nt->id;
  b = N_VInvTest_Serial(xarg(i), zarg(i));
  if (!b) { lockfalse; }
  return (void*)0;
}
booleantype N_VInvTest_NrnThreadLD(N_Vector x, N_Vector z)
{
  bretval = TRUE;
  xpass zpass
  nrn_multithread_job(vinvtest);
mydebug2("vinvtest %d\n", bretval);
  return(bretval);
}

static void* vconstrmask(NrnThread* nt) {
  booleantype b;
  int i = nt->id;
  b = N_VConstrMask_Serial(yarg(i), xarg(i), zarg(i));
  if (!b) { lockfalse; }
  return (void*)0;
}
booleantype N_VConstrMask_NrnThreadLD(N_Vector y, N_Vector x, N_Vector z)
{
  bretval = TRUE;
  ypass xpass zpass
  nrn_multithread_job(vconstrmask);
mydebug2("vconstrmask %d\n", bretval);
  return(bretval);
}

static void* vminquotient(NrnThread* nt) {
  realtype min;
  int i = nt->id;
  min = N_VMinQuotient_Serial(xarg(i), yarg(i));
  lockmin(min);
  return (void*)0;
}
realtype N_VMinQuotient_NrnThreadLD(N_Vector x, N_Vector y) /* num, denom */
{
  retval = BIG_REAL;
  xpass ypass
  nrn_multithread_job(vconstrmask);
mydebug2("vminquotient %.20g\n", retval);
  return(retval);
}
