text: #ifndef NDARRAYTYPES_H
  #define NDARRAYTYPES_H
  
  #include "npy_common.h"
  #include "npy_endian.h"
  #include "npy_cpu.h"
  #include "utils.h"
  
  #define NPY_NO_EXPORT NPY_VISIBILITY_HIDDEN
  
  /* Only use thread if configured in config and python supports it */
  #if defined WITH_THREAD && !NPY_NO_SMP
          #define NPY_ALLOW_THREADS 1
  #else
          #define NPY_ALLOW_THREADS 0
  #endif
  
  #ifndef __has_extension
  #define __has_extension(x) 0
  #endif
  
  #if !defined(_NPY_NO_DEPRECATIONS) && \
      ((defined(__GNUC__)&& __GNUC__ >= 6) || \
       __has_extension(attribute_deprecated_with_message))
  #define NPY_ATTR_DEPRECATE(text) __attribute__ ((deprecated (text)))
  #else
  #define NPY_ATTR_DEPRECATE(text)
  #endif
  
  /*
   * There are several places in the code where an array of dimensions
   * is allocated statically.  This is the size of that static
   * allocation.
   *
   * The array creation itself could have arbitrary dimensions but all
   * the places where static allocation is used would need to be changed
   * to dynamic (including inside of several structures)
   */
  
  #define NPY_MAXDIMS 32
  #define NPY_MAXARGS 32
  
  /* Used for Converter Functions "O&" code in ParseTuple */
  #define NPY_FAIL 0
  #define NPY_SUCCEED 1
  
  /*
   * Binary compatibility version number.  This number is increased
   * whenever the C-API is changed such that binary compatibility is
   * broken, i.e. whenever a recompile of extension modules is needed.
   */
  #define NPY_VERSION NPY_ABI_VERSION
  
  /*
   * Minor API version.  This number is increased whenever a change is
   * made to the C-API -- whether it breaks binary compatibility or not.
   * Some changes, such as adding a function pointer to the end of the
   * function table, can be made without breaking binary compatibility.
   * In this case, only the NPY_FEATURE_VERSION (*not* NPY_VERSION)
   * would be increased.  Whenever binary compatibility is broken, both
   * NPY_VERSION and NPY_FEATURE_VERSION should be increased.
   */
  #define NPY_FEATURE_VERSION NPY_API_VERSION
  
  enum NPY_TYPES {    NPY_BOOL=0,
                      NPY_BYTE, NPY_UBYTE,
                      NPY_SHORT, NPY_USHORT,
                      NPY_INT, NPY_UINT,
                      NPY_LONG, NPY_ULONG,
                      NPY_LONGLONG, NPY_ULONGLONG,
                      NPY_FLOAT, NPY_DOUBLE, NPY_LONGDOUBLE,
                      NPY_CFLOAT, NPY_CDOUBLE, NPY_CLONGDOUBLE,
                      NPY_OBJECT=17,
                      NPY_STRING, NPY_UNICODE,
                      NPY_VOID,
                      /*
                       * New 1.6 types appended, may be integrated
                       * into the above in 2.0.
                       */
                      NPY_DATETIME, NPY_TIMEDELTA, NPY_HALF,
  
                      NPY_NTYPES,
                      NPY_NOTYPE,
                      NPY_CHAR NPY_ATTR_DEPRECATE("Use NPY_STRING"),
                      NPY_USERDEF=256,  /* leave room for characters */
  
                      /* The number of types not including the new 1.6 types */
                      NPY_NTYPES_ABI_COMPATIBLE=21
  };
  #ifdef _MSC_VER
  #pragma deprecated(NPY_CHAR)
  #endif
  
  /* basetype array priority */
  #define NPY_PRIORITY 0.0
  
  /* default subtype priority */
  #define NPY_SUBTYPE_PRIORITY 1.0
  
  /* default scalar priority */
  #define NPY_SCALAR_PRIORITY -1000000.0
  
  /* How many floating point types are there (excluding half) */
  #define NPY_NUM_FLOATTYPE 3
  
  /*
   * These characters correspond to the array type and the struct
   * module
   */
  
  enum NPY_TYPECHAR {
          NPY_BOOLLTR = '?',
          NPY_BYTELTR = 'b',
          NPY_UBYTELTR = 'B',
          NPY_SHORTLTR = 'h',
          NPY_USHORTLTR = 'H',
          NPY_INTLTR = 'i',
          NPY_UINTLTR = 'I',
          NPY_LONGLTR = 'l',
          NPY_ULONGLTR = 'L',
          NPY_LONGLONGLTR = 'q',
          NPY_ULONGLONGLTR = 'Q',
          NPY_HALFLTR = 'e',
          NPY_FLOATLTR = 'f',
          NPY_DOUBLELTR = 'd',
          NPY_LONGDOUBLELTR = 'g',
          NPY_CFLOATLTR = 'F',
          NPY_CDOUBLELTR = 'D',
          NPY_CLONGDOUBLELTR = 'G',
          NPY_OBJECTLTR = 'O',
          NPY_STRINGLTR = 'S',
          NPY_STRINGLTR2 = 'a',
          NPY_UNICODELTR = 'U',
          NPY_VOIDLTR = 'V',
          NPY_DATETIMELTR = 'M',
          NPY_TIMEDELTALTR = 'm',
          NPY_CHARLTR = 'c',
  
          /*
           * No Descriptor, just a define -- this let's
           * Python users specify an array of integers
           * large enough to hold a pointer on the
           * platform
           */
          NPY_INTPLTR = 'p',
          NPY_UINTPLTR = 'P',
  
          /*
           * These are for dtype 'kinds', not dtype 'typecodes'
           * as the above are for.
           */
          NPY_GENBOOLLTR ='b',
          NPY_SIGNEDLTR = 'i',
          NPY_UNSIGNEDLTR = 'u',
          NPY_FLOATINGLTR = 'f',
          NPY_COMPLEXLTR = 'c'
  };
  
  /*
   * Changing this may break Numpy API compatibility
   * due to changing offsets in PyArray_ArrFuncs, so be
   * careful. Here we have reused the mergesort slot for
   * any kind of stable sort, the actual implementation will
   * depend on the data type.
   */
  typedef enum {
          NPY_QUICKSORT=0,
          NPY_HEAPSORT=1,
          NPY_MERGESORT=2,
          NPY_STABLESORT=2,
  } NPY_SORTKIND;
  #define NPY_NSORTS (NPY_STABLESORT + 1)
  
  
  typedef enum {
          NPY_INTROSELECT=0
  } NPY_SELECTKIND;
  #define NPY_NSELECTS (NPY_INTROSELECT + 1)
  
  
  typedef enum {
          NPY_SEARCHLEFT=0,
          NPY_SEARCHRIGHT=1
  } NPY_SEARCHSIDE;
  #define NPY_NSEARCHSIDES (NPY_SEARCHRIGHT + 1)
  
  
  typedef enum {
          NPY_NOSCALAR=-1,
          NPY_BOOL_SCALAR,
          NPY_INTPOS_SCALAR,
          NPY_INTNEG_SCALAR,
          NPY_FLOAT_SCALAR,
          NPY_COMPLEX_SCALAR,
          NPY_OBJECT_SCALAR
  } NPY_SCALARKIND;
  #define NPY_NSCALARKINDS (NPY_OBJECT_SCALAR + 1)
  
  /* For specifying array memory layout or iteration order */
  typedef enum {
          /* Fortran order if inputs are all Fortran, C otherwise */
          NPY_ANYORDER=-1,
          /* C order */
          NPY_CORDER=0,
          /* Fortran order */
          NPY_FORTRANORDER=1,
          /* An order as close to the inputs as possible */
          NPY_KEEPORDER=2
  } NPY_ORDER;
  
  /* For specifying allowed casting in operations which support it */
  typedef enum {
          _NPY_ERROR_OCCURRED_IN_CAST = -1,
          /* Only allow identical types */
          NPY_NO_CASTING=0,
          /* Allow identical and byte swapped types */
          NPY_EQUIV_CASTING=1,
          /* Only allow safe casts */
          NPY_SAFE_CASTING=2,
          /* Allow safe casts or casts within the same kind */
          NPY_SAME_KIND_CASTING=3,
          /* Allow any casts */
          NPY_UNSAFE_CASTING=4,
          /*
           * Flag to allow signalling that a cast is a view, this flag is not
           * valid when requesting a cast of specific safety.
           * _NPY_CAST_IS_VIEW|NPY_EQUIV_CASTING means the same as NPY_NO_CASTING.
           */
          // TODO-DTYPES: Needs to be documented.
          _NPY_CAST_IS_VIEW = 1 << 16,
  } NPY_CASTING;
  
  typedef enum {
          NPY_CLIP=0,
          NPY_WRAP=1,
          NPY_RAISE=2
  } NPY_CLIPMODE;
  
  /* The special not-a-time (NaT) value */
  #define NPY_DATETIME_NAT NPY_MIN_INT64
  
  /*
   * Upper bound on the length of a DATETIME ISO 8601 string
   *   YEAR: 21 (64-bit year)
   *   MONTH: 3
   *   DAY: 3
   *   HOURS: 3
   *   MINUTES: 3
   *   SECONDS: 3
   *   ATTOSECONDS: 1 + 3*6
   *   TIMEZONE: 5
   *   NULL TERMINATOR: 1
   */
  #define NPY_DATETIME_MAX_ISO8601_STRLEN (21 + 3*5 + 1 + 3*6 + 6 + 1)
  
  /* The FR in the unit names stands for frequency */
  typedef enum {
          /* Force signed enum type, must be -1 for code compatibility */
          NPY_FR_ERROR = -1,      /* error or undetermined */
  
          /* Start of valid units */
          NPY_FR_Y = 0,           /* Years */
          NPY_FR_M = 1,           /* Months */
          NPY_FR_W = 2,           /* Weeks */
          /* Gap where 1.6 NPY_FR_B (value 3) was */
          NPY_FR_D = 4,           /* Days */
          NPY_FR_h = 5,           /* hours */
          NPY_FR_m = 6,           /* minutes */
          NPY_FR_s = 7,           /* seconds */
          NPY_FR_ms = 8,          /* milliseconds */
          NPY_FR_us = 9,          /* microseconds */
          NPY_FR_ns = 10,         /* nanoseconds */
          NPY_FR_ps = 11,         /* picoseconds */
          NPY_FR_fs = 12,         /* femtoseconds */
          NPY_FR_as = 13,         /* attoseconds */
          NPY_FR_GENERIC = 14     /* unbound units, can convert to anything */
  } NPY_DATETIMEUNIT;
  
  /*
   * NOTE: With the NPY_FR_B gap for 1.6 ABI compatibility, NPY_DATETIME_NUMUNITS
   * is technically one more than the actual number of units.
   */
  #define NPY_DATETIME_NUMUNITS (NPY_FR_GENERIC + 1)
  #define NPY_DATETIME_DEFAULTUNIT NPY_FR_GENERIC
  
  /*
   * Business day conventions for mapping invalid business
   * days to valid business days.
   */
  typedef enum {
      /* Go forward in time to the following business day. */
      NPY_BUSDAY_FORWARD,
      NPY_BUSDAY_FOLLOWING = NPY_BUSDAY_FORWARD,
      /* Go backward in time to the preceding business day. */
      NPY_BUSDAY_BACKWARD,
      NPY_BUSDAY_PRECEDING = NPY_BUSDAY_BACKWARD,
      /*
       * Go forward in time to the following business day, unless it
       * crosses a month boundary, in which case go backward
       */
      NPY_BUSDAY_MODIFIEDFOLLOWING,
      /*
       * Go backward in time to the preceding business day, unless it
       * crosses a month boundary, in which case go forward.
       */
      NPY_BUSDAY_MODIFIEDPRECEDING,
      /* Produce a NaT for non-business days. */
      NPY_BUSDAY_NAT,
      /* Raise an exception for non-business days. */
      NPY_BUSDAY_RAISE
  } NPY_BUSDAY_ROLL;
  
  /************************************************************
   * NumPy Auxiliary Data for inner loops, sort functions, etc.
   ************************************************************/
  
  /*
   * When creating an auxiliary data struct, this should always appear
   * as the first member, like this:
   *
   * typedef struct {
   *     NpyAuxData base;
   *     double constant;
   * } constant_multiplier_aux_data;
   */
  typedef struct NpyAuxData_tag NpyAuxData;
  
  /* Function pointers for freeing or cloning auxiliary data */
  typedef void (NpyAuxData_FreeFunc) (NpyAuxData *);
  typedef NpyAuxData *(NpyAuxData_CloneFunc) (NpyAuxData *);
  
  struct NpyAuxData_tag {
      NpyAuxData_FreeFunc *free;
      NpyAuxData_CloneFunc *clone;
      /* To allow for a bit of expansion without breaking the ABI */
      void *reserved[2];
  };
  
  /* Macros to use for freeing and cloning auxiliary data */
  #define NPY_AUXDATA_FREE(auxdata) \
      do { \
          if ((auxdata) != NULL) { \
              (auxdata)->free(auxdata); \
          } \
      } while(0)
  #define NPY_AUXDATA_CLONE(auxdata) \
      ((auxdata)->clone(auxdata))
  
  #define NPY_ERR(str) fprintf(stderr, #str); fflush(stderr);
  #define NPY_ERR2(str) fprintf(stderr, str); fflush(stderr);
  
    /*
     * Macros to define how array, and dimension/strides data is
     * allocated.
     */
  
    /* Data buffer - PyDataMem_NEW/FREE/RENEW are in multiarraymodule.c */
  
  #define NPY_USE_PYMEM 1
  
  
  #if NPY_USE_PYMEM == 1
  /* use the Raw versions which are safe to call with the GIL released */
  #define PyArray_malloc PyMem_RawMalloc
  #define PyArray_free PyMem_RawFree
  #define PyArray_realloc PyMem_RawRealloc
  #else
  #define PyArray_malloc malloc
  #define PyArray_free free
  #define PyArray_realloc realloc
  #endif
  
  /* Dimensions and strides */
  #define PyDimMem_NEW(size)                                         \
      ((npy_intp *)PyArray_malloc(size*sizeof(npy_intp)))
  
  #define PyDimMem_FREE(ptr) PyArray_free(ptr)
  
  #define PyDimMem_RENEW(ptr,size)                                   \
          ((npy_intp *)PyArray_realloc(ptr,size*sizeof(npy_intp)))
  
  /* forward declaration */
  struct _PyArray_Descr;
  
  /* These must deal with unaligned and swapped data if necessary */
  typedef PyObject * (PyArray_GetItemFunc) (void *, void *);
  typedef int (PyArray_SetItemFunc)(PyObject *, void *, void *);
  
  typedef void (PyArray_CopySwapNFunc)(void *, npy_intp, void *, npy_intp,
                                       npy_intp, int, void *);
  
  typedef void (PyArray_CopySwapFunc)(void *, void *, int, void *);
  typedef npy_bool (PyArray_NonzeroFunc)(void *, void *);
  
  
  /*
   * These assume aligned and notswapped data -- a buffer will be used
   * before or contiguous data will be obtained
   */
  
  typedef int (PyArray_CompareFunc)(const void *, const void *, void *);
  typedef int (PyArray_ArgFunc)(void*, npy_intp, npy_intp*, void *);
  
  typedef void (PyArray_DotFunc)(void *, npy_intp, void *, npy_intp, void *,
                                 npy_intp, void *);
  
  typedef void (PyArray_VectorUnaryFunc)(void *, void *, npy_intp, void *,
                                         void *);
  
  /*
   * XXX the ignore argument should be removed next time the API version
   * is bumped. It used to be the separator.
   */
  typedef int (PyArray_ScanFunc)(FILE *fp, void *dptr,
                                 char *ignore, struct _PyArray_Descr *);
  typedef int (PyArray_FromStrFunc)(char *s, void *dptr, char **endptr,
                                    struct _PyArray_Descr *);
  
  typedef int (PyArray_FillFunc)(void *, npy_intp, void *);
  
  typedef int (PyArray_SortFunc)(void *, npy_intp, void *);
  typedef int (PyArray_ArgSortFunc)(void *, npy_intp *, npy_intp, void *);
  typedef int (PyArray_PartitionFunc)(void *, npy_intp, npy_intp,
                                      npy_intp *, npy_intp *,
                                      void *);
  typedef int (PyArray_ArgPartitionFunc)(void *, npy_intp *, npy_intp, npy_intp,
                                         npy_intp *, npy_intp *,
                                         void *);
  
  typedef int (PyArray_FillWithScalarFunc)(void *, npy_intp, void *, void *);
  
  typedef int (PyArray_ScalarKindFunc)(void *);
  
  typedef void (PyArray_FastClipFunc)(void *in, npy_intp n_in, void *min,
                                      void *max, void *out);
  typedef void (PyArray_FastPutmaskFunc)(void *in, void *mask, npy_intp n_in,
                                         void *values, npy_intp nv);
  typedef int  (PyArray_FastTakeFunc)(void *dest, void *src, npy_intp *indarray,
                                         npy_intp nindarray, npy_intp n_outer,
                                         npy_intp m_middle, npy_intp nelem,
                                         NPY_CLIPMODE clipmode);
  
  typedef struct {
          npy_intp *ptr;
          int len;
  } PyArray_Dims;
  
  typedef struct {
          /*
           * Functions to cast to most other standard types
           * Can have some NULL entries. The types
           * DATETIME, TIMEDELTA, and HALF go into the castdict
           * even though they are built-in.
           */
          PyArray_VectorUnaryFunc *cast[NPY_NTYPES_ABI_COMPATIBLE];
  
          /* The next four functions *cannot* be NULL */
  
          /*
           * Functions to get and set items with standard Python types
           * -- not array scalars
           */
          PyArray_GetItemFunc *getitem;
          PyArray_SetItemFunc *setitem;
  
          /*
           * Copy and/or swap data.  Memory areas may not overlap
           * Use memmove first if they might
           */
          PyArray_CopySwapNFunc *copyswapn;
          PyArray_CopySwapFunc *copyswap;
  
          /*
           * Function to compare items
           * Can be NULL
           */
          PyArray_CompareFunc *compare;
  
          /*
           * Function to select largest
           * Can be NULL
           */
          PyArray_ArgFunc *argmax;
  
          /*
           * Function to compute dot product
           * Can be NULL
           */
          PyArray_DotFunc *dotfunc;
  
          /*
           * Function to scan an ASCII file and
           * place a single value plus possible separator
           * Can be NULL
           */
          PyArray_ScanFunc *scanfunc;
  
          /*
           * Function to read a single value from a string
           * and adjust the pointer; Can be NULL
           */
          PyArray_FromStrFunc *fromstr;
  
          /*
           * Function to determine if data is zero or not
           * If NULL a default version is
           * used at Registration time.
           */
          PyArray_NonzeroFunc *nonzero;
  
          /*
           * Used for arange. Should return 0 on success
           * and -1 on failure.
           * Can be NULL.
           */
          PyArray_FillFunc *fill;
  
          /*
           * Function to fill arrays with scalar values
           * Can be NULL
           */
          PyArray_FillWithScalarFunc *fillwithscalar;
  
          /*
           * Sorting functions
           * Can be NULL
           */
          PyArray_SortFunc *sort[NPY_NSORTS];
          PyArray_ArgSortFunc *argsort[NPY_NSORTS];
  
          /*
           * Dictionary of additional casting functions
           * PyArray_VectorUnaryFuncs
           * which can be populated to support casting
           * to other registered types. Can be NULL
           */
          PyObject *castdict;
  
          /*
           * Functions useful for generalizing
           * the casting rules.
           * Can be NULL;
           */
          PyArray_ScalarKindFunc *scalarkind;
          int **cancastscalarkindto;
          int *cancastto;
  
          PyArray_FastClipFunc *fastclip;
          PyArray_FastPutmaskFunc *fastputmask;
          PyArray_FastTakeFunc *fasttake;
  
          /*
           * Function to select smallest
           * Can be NULL
           */
          PyArray_ArgFunc *argmin;
  
  } PyArray_ArrFuncs;
  
  /* The item must be reference counted when it is inserted or extracted. */
  #define NPY_ITEM_REFCOUNT   0x01
  /* Same as needing REFCOUNT */
  #define NPY_ITEM_HASOBJECT  0x01
  /* Convert to list for pickling */
  #define NPY_LIST_PICKLE     0x02
  /* The item is a POINTER  */
  #define NPY_ITEM_IS_POINTER 0x04
  /* memory needs to be initialized for this data-type */
  #define NPY_NEEDS_INIT      0x08
  /* operations need Python C-API so don't give-up thread. */
  #define NPY_NEEDS_PYAPI     0x10
  /* Use f.getitem when extracting elements of this data-type */
  #define NPY_USE_GETITEM     0x20
  /* Use f.setitem when setting creating 0-d array from this data-type.*/
  #define NPY_USE_SETITEM     0x40
  /* A sticky flag specifically for structured arrays */
  #define NPY_ALIGNED_STRUCT  0x80
  
  /*
   *These are inherited for global data-type if any data-types in the
   * field have them
   */
  #define NPY_FROM_FIELDS    (NPY_NEEDS_INIT | NPY_LIST_PICKLE | \
                              NPY_ITEM_REFCOUNT | NPY_NEEDS_PYAPI)
  
  #define NPY_OBJECT_DTYPE_FLAGS (NPY_LIST_PICKLE | NPY_USE_GETITEM | \
                                  NPY_ITEM_IS_POINTER | NPY_ITEM_REFCOUNT | \
                                  NPY_NEEDS_INIT | NPY_NEEDS_PYAPI)
  
  #define PyDataType_FLAGCHK(dtype, flag) \
          (((dtype)->flags & (flag)) == (flag))
  
  #define PyDataType_REFCHK(dtype) \
          PyDataType_FLAGCHK(dtype, NPY_ITEM_REFCOUNT)
  
  typedef struct _PyArray_Descr {
          PyObject_HEAD
          /*
           * the type object representing an
           * instance of this type -- should not
           * be two type_numbers with the same type
           * object.
           */
          PyTypeObject *typeobj;
          /* kind for this type */
          char kind;
          /* unique-character representing this type */
          char type;
          /*
           * '>' (big), '<' (little), '|'
           * (not-applicable), or '=' (native).
           */
          char byteorder;
          /* flags describing data type */
          char flags;
          /* number representing this type */
          int type_num;
          /* element size (itemsize) for this type */
          int elsize;
          /* alignment needed for this type */
          int alignment;
          /*
           * Non-NULL if this type is
           * is an array (C-contiguous)
           * of some other type
           */
          struct _arr_descr *subarray;
          /*
           * The fields dictionary for this type
           * For statically defined descr this
           * is always Py_None
           */
          PyObject *fields;
          /*
           * An ordered tuple of field names or NULL
           * if no fields are defined
           */
          PyObject *names;
          /*
           * a table of functions specific for each
           * basic data descriptor
           */
          PyArray_ArrFuncs *f;
          /* Metadata about this dtype */
          PyObject *metadata;
          /*
           * Metadata specific to the C implementation
           * of the particular dtype. This was added
           * for NumPy 1.7.0.
           */
          NpyAuxData *c_metadata;
          /* Cached hash value (-1 if not yet computed).
           * This was added for NumPy 2.0.0.
           */
          npy_hash_t hash;
  } PyArray_Descr;
  
  typedef struct _arr_descr {
          PyArray_Descr *base;
          PyObject *shape;       /* a tuple */
  } PyArray_ArrayDescr;
  
  /*
   * The main array object structure.
   *
   * It has been recommended to use the inline functions defined below
   * (PyArray_DATA and friends) to access fields here for a number of
   * releases. Direct access to the members themselves is deprecated.
   * To ensure that your code does not use deprecated access,
   * #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
   * (or NPY_1_8_API_VERSION or higher as required).
   */
  /* This struct will be moved to a private header in a future release */
  typedef struct tagPyArrayObject_fields {
      PyObject_HEAD
      /* Pointer to the raw data buffer */
      char *data;
      /* The number of dimensions, also called 'ndim' */
      int nd;
      /* The size in each dimension, also called 'shape' */
      npy_intp *dimensions;
      /*
       * Number of bytes to jump to get to the
       * next element in each dimension
       */
      npy_intp *strides;
      /*
       * This object is decref'd upon
       * deletion of array. Except in the
       * case of WRITEBACKIFCOPY which has
       * special handling.
       *
       * For views it points to the original
       * array, collapsed so no chains of
       * views occur.
       *
       * For creation from buffer object it
       * points to an object that should be
       * decref'd on deletion
       *
       * For WRITEBACKIFCOPY flag this is an
       * array to-be-updated upon calling
       * PyArray_ResolveWritebackIfCopy
       */
      PyObject *base;
      /* Pointer to type structure */
      PyArray_Descr *descr;
      /* Flags describing array -- see below */
      int flags;
      /* For weak references */
      PyObject *weakreflist;
      void *_buffer_info;  /* private buffer info, tagged to allow warning */
  } PyArrayObject_fields;
  
  /*
   * To hide the implementation details, we only expose
   * the Python struct HEAD.
   */
  #if !defined(NPY_NO_DEPRECATED_API) || \
      (NPY_NO_DEPRECATED_API < NPY_1_7_API_VERSION)
  /*
   * Can't put this in npy_deprecated_api.h like the others.
   * PyArrayObject field access is deprecated as of NumPy 1.7.
   */
  typedef PyArrayObject_fields PyArrayObject;
  #else
  typedef struct tagPyArrayObject {
          PyObject_HEAD
  } PyArrayObject;
  #endif
  
  /*
   * Removed 2020-Nov-25, NumPy 1.20
   * #define NPY_SIZEOF_PYARRAYOBJECT (sizeof(PyArrayObject_fields))
   *
   * The above macro was removed as it gave a false sense of a stable ABI
   * with respect to the structures size.  If you require a runtime constant,
   * you can use `PyArray_Type.tp_basicsize` instead.  Otherwise, please
   * see the PyArrayObject documentation or ask the NumPy developers for
   * information on how to correctly replace the macro in a way that is
   * compatible with multiple NumPy versions.
   */
  
  
  /* Array Flags Object */
  typedef struct PyArrayFlagsObject {
          PyObject_HEAD
          PyObject *arr;
          int flags;
  } PyArrayFlagsObject;
  
  /* Mirrors buffer object to ptr */
  
  typedef struct {
          PyObject_HEAD
          PyObject *base;
          void *ptr;
          npy_intp len;
          int flags;
  } PyArray_Chunk;
  
  typedef struct {
      NPY_DATETIMEUNIT base;
      int num;
  } PyArray_DatetimeMetaData;
  
  typedef struct {
      NpyAuxData base;
      PyArray_DatetimeMetaData meta;
  } PyArray_DatetimeDTypeMetaData;
  
  /*
   * This structure contains an exploded view of a date-time value.
   * NaT is represented by year == NPY_DATETIME_NAT.
   */
  typedef struct {
          npy_int64 year;
          npy_int32 month, day, hour, min, sec, us, ps, as;
  } npy_datetimestruct;
  
  /* This is not used internally. */
  typedef struct {
          npy_int64 day;
          npy_int32 sec, us, ps, as;
  } npy_timedeltastruct;
  
  typedef int (PyArray_FinalizeFunc)(PyArrayObject *, PyObject *);
  
  /*
   * Means c-style contiguous (last index varies the fastest). The data
   * elements right after each other.
   *
   * This flag may be requested in constructor functions.
   * This flag may be tested for in PyArray_FLAGS(arr).
   */
  #define NPY_ARRAY_C_CONTIGUOUS    0x0001
  
  /*
   * Set if array is a contiguous Fortran array: the first index varies
   * the fastest in memory (strides array is reverse of C-contiguous
   * array)
   *
   * This flag may be requested in constructor functions.
   * This flag may be tested for in PyArray_FLAGS(arr).
   */
  #define NPY_ARRAY_F_CONTIGUOUS    0x0002
  
  /*
   * Note: all 0-d arrays are C_CONTIGUOUS and F_CONTIGUOUS. If a
   * 1-d array is C_CONTIGUOUS it is also F_CONTIGUOUS. Arrays with
   * more then one dimension can be C_CONTIGUOUS and F_CONTIGUOUS
   * at the same time if they have either zero or one element.
   * If NPY_RELAXED_STRIDES_CHECKING is set, a higher dimensional
   * array is always C_CONTIGUOUS and F_CONTIGUOUS if it has zero elements
   * and the array is contiguous if ndarray.squeeze() is contiguous.
   * I.e. dimensions for which `ndarray.shape[dimension] == 1` are
   * ignored.
   */
  
  /*
   * If set, the array owns the data: it will be free'd when the array
   * is deleted.
   *
   * This flag may be tested for in PyArray_FLAGS(arr).
   */
  #define NPY_ARRAY_OWNDATA         0x0004
  
  /*
   * An array never has the next four set; they're only used as parameter
   * flags to the various FromAny functions
   *
   * This flag may be requested in constructor functions.
   */
  
  /* Cause a cast to occur regardless of whether or not it is safe. */
  #define NPY_ARRAY_FORCECAST       0x0010
  
  /*
   * Always copy the array. Returned arrays are always CONTIGUOUS,
   * ALIGNED, and WRITEABLE.
   *
   * This flag may be requested in constructor functions.
   */
  #define NPY_ARRAY_ENSURECOPY      0x0020
  
  /*
   * Make sure the returned array is a base-class ndarray
   *
   * This flag may be requested in constructor functions.
   */
  #define NPY_ARRAY_ENSUREARRAY     0x0040
  
  /*
   * Make sure that the strides are in units of the element size Needed
   * for some operations with record-arrays.
   *
   * This flag may be requested in constructor functions.
   */
  #define NPY_ARRAY_ELEMENTSTRIDES  0x0080
  
  /*
   * Array data is aligned on the appropriate memory address for the type
   * stored according to how the compiler would align things (e.g., an
   * array of integers (4 bytes each) starts on a memory address that's
   * a multiple of 4)
   *
   * This flag may be requested in constructor functions.
   * This flag may be tested for in PyArray_FLAGS(arr).
   */
  #define NPY_ARRAY_ALIGNED         0x0100
  
  /*
   * Array data has the native endianness
   *
   * This flag may be requested in constructor functions.
   */
  #define NPY_ARRAY_NOTSWAPPED      0x0200
  
  /*
   * Array data is writeable
   *
   * This flag may be requested in constructor functions.
   * This flag may be tested for in PyArray_FLAGS(arr).
   */
  #define NPY_ARRAY_WRITEABLE       0x0400
  
  /*
   * If this flag is set, then base contains a pointer to an array of
   * the same size that should be updated with the current contents of
   * this array when PyArray_ResolveWritebackIfCopy is called.
   *
   * This flag may be requested in constructor functions.
   * This flag may be tested for in PyArray_FLAGS(arr).
   */
  #define NPY_ARRAY_UPDATEIFCOPY    0x1000 /* Deprecated in 1.14 */
  #define NPY_ARRAY_WRITEBACKIFCOPY 0x2000
  
  /*
   * NOTE: there are also internal flags defined in multiarray/arrayobject.h,
   * which start at bit 31 and work down.
   */
  
  #define NPY_ARRAY_BEHAVED      (NPY_ARRAY_ALIGNED | \
                                  NPY_ARRAY_WRITEABLE)
  #define NPY_ARRAY_BEHAVED_NS   (NPY_ARRAY_ALIGNED | \
                                  NPY_ARRAY_WRITEABLE | \
                                  NPY_ARRAY_NOTSWAPPED)
  #define NPY_ARRAY_CARRAY       (NPY_ARRAY_C_CONTIGUOUS | \
                                  NPY_ARRAY_BEHAVED)
  #define NPY_ARRAY_CARRAY_RO    (NPY_ARRAY_C_CONTIGUOUS | \
                                  NPY_ARRAY_ALIGNED)
  #define NPY_ARRAY_FARRAY       (NPY_ARRAY_F_CONTIGUOUS | \
                                  NPY_ARRAY_BEHAVED)
  #define NPY_ARRAY_FARRAY_RO    (NPY_ARRAY_F_CONTIGUOUS | \
                                  NPY_ARRAY_ALIGNED)
  #define NPY_ARRAY_DEFAULT      (NPY_ARRAY_CARRAY)
  #define NPY_ARRAY_IN_ARRAY     (NPY_ARRAY_CARRAY_RO)
  #define NPY_ARRAY_OUT_ARRAY    (NPY_ARRAY_CARRAY)
  #define NPY_ARRAY_INOUT_ARRAY  (NPY_ARRAY_CARRAY | \
                                  NPY_ARRAY_UPDATEIFCOPY)
  #define NPY_ARRAY_INOUT_ARRAY2 (NPY_ARRAY_CARRAY | \
                                  NPY_ARRAY_WRITEBACKIFCOPY)
  #define NPY_ARRAY_IN_FARRAY    (NPY_ARRAY_FARRAY_RO)
  #define NPY_ARRAY_OUT_FARRAY   (NPY_ARRAY_FARRAY)
  #define NPY_ARRAY_INOUT_FARRAY (NPY_ARRAY_FARRAY | \
                                  NPY_ARRAY_UPDATEIFCOPY)
  #define NPY_ARRAY_INOUT_FARRAY2 (NPY_ARRAY_FARRAY | \
                                  NPY_ARRAY_WRITEBACKIFCOPY)
  
  #define NPY_ARRAY_UPDATE_ALL   (NPY_ARRAY_C_CONTIGUOUS | \
                                  NPY_ARRAY_F_CONTIGUOUS | \
                                  NPY_ARRAY_ALIGNED)
  
  /* This flag is for the array interface, not PyArrayObject */
  #define NPY_ARR_HAS_DESCR  0x0800
  
  
  
  
  /*
   * Size of internal buffers used for alignment Make BUFSIZE a multiple
   * of sizeof(npy_cdouble) -- usually 16 so that ufunc buffers are aligned
   */
  #define NPY_MIN_BUFSIZE ((int)sizeof(npy_cdouble))
  #define NPY_MAX_BUFSIZE (((int)sizeof(npy_cdouble))*1000000)
  #define NPY_BUFSIZE 8192
  /* buffer stress test size: */
  /*#define NPY_BUFSIZE 17*/
  
  #define PyArray_MAX(a,b) (((a)>(b))?(a):(b))
  #define PyArray_MIN(a,b) (((a)<(b))?(a):(b))
  #define PyArray_CLT(p,q) ((((p).real==(q).real) ? ((p).imag < (q).imag) : \
                                 ((p).real < (q).real)))
  #define PyArray_CGT(p,q) ((((p).real==(q).real) ? ((p).imag > (q).imag) : \
                                 ((p).real > (q).real)))
  #define PyArray_CLE(p,q) ((((p).real==(q).real) ? ((p).imag <= (q).imag) : \
                                 ((p).real <= (q).real)))
  #define PyArray_CGE(p,q) ((((p).real==(q).real) ? ((p).imag >= (q).imag) : \
                                 ((p).real >= (q).real)))
  #define PyArray_CEQ(p,q) (((p).real==(q).real) && ((p).imag == (q).imag))
  #define PyArray_CNE(p,q) (((p).real!=(q).real) || ((p).imag != (q).imag))
  
  /*
   * C API: consists of Macros and functions.  The MACROS are defined
   * here.
   */
  
  
  #define PyArray_ISCONTIGUOUS(m) PyArray_CHKFLAGS((m), NPY_ARRAY_C_CONTIGUOUS)
  #define PyArray_ISWRITEABLE(m) PyArray_CHKFLAGS((m), NPY_ARRAY_WRITEABLE)
  #define PyArray_ISALIGNED(m) PyArray_CHKFLAGS((m), NPY_ARRAY_ALIGNED)
  
  #define PyArray_IS_C_CONTIGUOUS(m) PyArray_CHKFLAGS((m), NPY_ARRAY_C_CONTIGUOUS)
  #define PyArray_IS_F_CONTIGUOUS(m) PyArray_CHKFLAGS((m), NPY_ARRAY_F_CONTIGUOUS)
  
  /* the variable is used in some places, so always define it */
  #define NPY_BEGIN_THREADS_DEF PyThreadState *_save=NULL;
  #if NPY_ALLOW_THREADS
  #define NPY_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
  #define NPY_END_ALLOW_THREADS Py_END_ALLOW_THREADS
  #define NPY_BEGIN_THREADS do {_save = PyEval_SaveThread();} while (0);
  #define NPY_END_THREADS   do { if (_save) \
                  { PyEval_RestoreThread(_save); _save = NULL;} } while (0);
  #define NPY_BEGIN_THREADS_THRESHOLDED(loop_size) do { if ((loop_size) > 500) \
                  { _save = PyEval_SaveThread();} } while (0);
  
  #define NPY_BEGIN_THREADS_DESCR(dtype) \
          do {if (!(PyDataType_FLAGCHK((dtype), NPY_NEEDS_PYAPI))) \
                  NPY_BEGIN_THREADS;} while (0);
  
  #define NPY_END_THREADS_DESCR(dtype) \
          do {if (!(PyDataType_FLAGCHK((dtype), NPY_NEEDS_PYAPI))) \
                  NPY_END_THREADS; } while (0);
  
  #define NPY_ALLOW_C_API_DEF  PyGILState_STATE __save__;
  #define NPY_ALLOW_C_API      do {__save__ = PyGILState_Ensure();} while (0);
  #define NPY_DISABLE_C_API    do {PyGILState_Release(__save__);} while (0);
  #else
  #define NPY_BEGIN_ALLOW_THREADS
  #define NPY_END_ALLOW_THREADS
  #define NPY_BEGIN_THREADS
  #define NPY_END_THREADS
  #define NPY_BEGIN_THREADS_THRESHOLDED(loop_size)
  #define NPY_BEGIN_THREADS_DESCR(dtype)
  #define NPY_END_THREADS_DESCR(dtype)
  #define NPY_ALLOW_C_API_DEF
  #define NPY_ALLOW_C_API
  #define NPY_DISABLE_C_API
  #endif
  
  /**********************************
   * The nditer object, added in 1.6
   **********************************/
  
  /* The actual structure of the iterator is an internal detail */
  typedef struct NpyIter_InternalOnly NpyIter;
  
  /* Iterator function pointers that may be specialized */
  typedef int (NpyIter_IterNextFunc)(NpyIter *iter);
  typedef void (NpyIter_GetMultiIndexFunc)(NpyIter *iter,
                                        npy_intp *outcoords);
  
  /*** Global flags that may be passed to the iterator constructors ***/
  
  /* Track an index representing C order */
  #define NPY_ITER_C_INDEX                    0x00000001
  /* Track an index representing Fortran order */
  #define NPY_ITER_F_INDEX                    0x00000002
  /* Track a multi-index */
  #define NPY_ITER_MULTI_INDEX                0x00000004
  /* User code external to the iterator does the 1-dimensional innermost loop */
  #define NPY_ITER_EXTERNAL_LOOP              0x00000008
  /* Convert all the operands to a common data type */
  #define NPY_ITER_COMMON_DTYPE               0x00000010
  /* Operands may hold references, requiring API access during iteration */
  #define NPY_ITER_REFS_OK                    0x00000020
  /* Zero-sized operands should be permitted, iteration checks IterSize for 0 */
  #define NPY_ITER_ZEROSIZE_OK                0x00000040
  /* Permits reductions (size-0 stride with dimension size > 1) */
  #define NPY_ITER_REDUCE_OK                  0x00000080
  /* Enables sub-range iteration */
  #define NPY_ITER_RANGED                     0x00000100
  /* Enables buffering */
  #define NPY_ITER_BUFFERED                   0x00000200
  /* When buffering is enabled, grows the inner loop if possible */
  #define NPY_ITER_GROWINNER                  0x00000400
  /* Delay allocation of buffers until first Reset* call */
  #define NPY_ITER_DELAY_BUFALLOC             0x00000800
  /* When NPY_KEEPORDER is specified, disable reversing negative-stride axes */
  #define NPY_ITER_DONT_NEGATE_STRIDES        0x00001000
  /*
   * If output operands overlap with other operands (based on heuristics that
   * has false positives but no false negatives), make temporary copies to
   * eliminate overlap.
   */
  #define NPY_ITER_COPY_IF_OVERLAP            0x00002000
  
  /*** Per-operand flags that may be passed to the iterator constructors ***/
  
  /* The operand will be read from and written to */
  #define NPY_ITER_READWRITE                  0x00010000
  /* The operand will only be read from */
  #define NPY_ITER_READONLY                   0x00020000
  /* The operand will only be written to */
  #define NPY_ITER_WRITEONLY                  0x00040000
  /* The operand's data must be in native byte order */
  #define NPY_ITER_NBO                        0x00080000
  /* The operand's data must be aligned */
  #define NPY_ITER_ALIGNED                    0x00100000
  /* The operand's data must be contiguous (within the inner loop) */
  #define NPY_ITER_CONTIG                     0x00200000
  /* The operand may be copied to satisfy requirements */
  #define NPY_ITER_COPY                       0x00400000
  /* The operand may be copied with WRITEBACKIFCOPY to satisfy requirements */
  #define NPY_ITER_UPDATEIFCOPY               0x00800000
  /* Allocate the operand if it is NULL */
  #define NPY_ITER_ALLOCATE                   0x01000000
  /* If an operand is allocated, don't use any subtype */
  #define NPY_ITER_NO_SUBTYPE                 0x02000000
  /* This is a virtual array slot, operand is NULL but temporary data is there */
  #define NPY_ITER_VIRTUAL                    0x04000000
  /* Require that the dimension match the iterator dimensions exactly */
  #define NPY_ITER_NO_BROADCAST               0x08000000
  /* A mask is being used on this array, affects buffer -> array copy */
  #define NPY_ITER_WRITEMASKED                0x10000000
  /* This array is the mask for all WRITEMASKED operands */
  #define NPY_ITER_ARRAYMASK                  0x20000000
  /* Assume iterator order data access for COPY_IF_OVERLAP */
  #define NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE 0x40000000
  
  #define NPY_ITER_GLOBAL_FLAGS               0x0000ffff
  #define NPY_ITER_PER_OP_FLAGS               0xffff0000
  
  
  /*****************************
   * Basic iterator object
   *****************************/
  
  /* FWD declaration */
  typedef struct PyArrayIterObject_tag PyArrayIterObject;
  
  /*
   * type of the function which translates a set of coordinates to a
   * pointer to the data
   */
  typedef char* (*npy_iter_get_dataptr_t)(
          PyArrayIterObject* iter, const npy_intp*);
  
  struct PyArrayIterObject_tag {
          PyObject_HEAD
          int               nd_m1;            /* number of dimensions - 1 */
          npy_intp          index, size;
          npy_intp          coordinates[NPY_MAXDIMS];/* N-dimensional loop */
          npy_intp          dims_m1[NPY_MAXDIMS];    /* ao->dimensions - 1 */
          npy_intp          strides[NPY_MAXDIMS];    /* ao->strides or fake */
          npy_intp          backstrides[NPY_MAXDIMS];/* how far to jump back */
          npy_intp          factors[NPY_MAXDIMS];     /* shape factors */
          PyArrayObject     *ao;
          char              *dataptr;        /* pointer to current item*/
          npy_bool          contiguous;
  
          npy_intp          bounds[NPY_MAXDIMS][2];
          npy_intp          limits[NPY_MAXDIMS][2];
          npy_intp          limits_sizes[NPY_MAXDIMS];
          npy_iter_get_dataptr_t translate;
  } ;
  
  
  /* Iterator API */
  #define PyArrayIter_Check(op) PyObject_TypeCheck((op), &PyArrayIter_Type)
  
  #define _PyAIT(it) ((PyArrayIterObject *)(it))
  #define PyArray_ITER_RESET(it) do { \
          _PyAIT(it)->index = 0; \
          _PyAIT(it)->dataptr = PyArray_BYTES(_PyAIT(it)->ao); \
          memset(_PyAIT(it)->coordinates, 0, \
                 (_PyAIT(it)->nd_m1+1)*sizeof(npy_intp)); \
  } while (0)
  
  #define _PyArray_ITER_NEXT1(it) do { \
          (it)->dataptr += _PyAIT(it)->strides[0]; \
          (it)->coordinates[0]++; \
  } while (0)
  
  #define _PyArray_ITER_NEXT2(it) do { \
          if ((it)->coordinates[1] < (it)->dims_m1[1]) { \
                  (it)->coordinates[1]++; \
                  (it)->dataptr += (it)->strides[1]; \
          } \
          else { \
                  (it)->coordinates[1] = 0; \
                  (it)->coordinates[0]++; \
                  (it)->dataptr += (it)->strides[0] - \
                          (it)->backstrides[1]; \
          } \
  } while (0)
  
  #define PyArray_ITER_NEXT(it) do { \
          _PyAIT(it)->index++; \
          if (_PyAIT(it)->nd_m1 == 0) { \
                  _PyArray_ITER_NEXT1(_PyAIT(it)); \
          } \
          else if (_PyAIT(it)->contiguous) \
                  _PyAIT(it)->dataptr += PyArray_DESCR(_PyAIT(it)->ao)->elsize; \
          else if (_PyAIT(it)->nd_m1 == 1) { \
                  _PyArray_ITER_NEXT2(_PyAIT(it)); \
          } \
          else { \
                  int __npy_i; \
                  for (__npy_i=_PyAIT(it)->nd_m1; __npy_i >= 0; __npy_i--) { \
                          if (_PyAIT(it)->coordinates[__npy_i] < \
                              _PyAIT(it)->dims_m1[__npy_i]) { \
                                  _PyAIT(it)->coordinates[__npy_i]++; \
                                  _PyAIT(it)->dataptr += \
                                          _PyAIT(it)->strides[__npy_i]; \
                                  break; \
                          } \
                          else { \
                                  _PyAIT(it)->coordinates[__npy_i] = 0; \
                                  _PyAIT(it)->dataptr -= \
                                          _PyAIT(it)->backstrides[__npy_i]; \
                          } \
                  } \
          } \
  } while (0)
  
  #define PyArray_ITER_GOTO(it, destination) do { \
          int __npy_i; \
          _PyAIT(it)->index = 0; \
          _PyAIT(it)->dataptr = PyArray_BYTES(_PyAIT(it)->ao); \
          for (__npy_i = _PyAIT(it)->nd_m1; __npy_i>=0; __npy_i--) { \
                  if (destination[__npy_i] < 0) { \
                          destination[__npy_i] += \
                                  _PyAIT(it)->dims_m1[__npy_i]+1; \
                  } \
                  _PyAIT(it)->dataptr += destination[__npy_i] * \
                          _PyAIT(it)->strides[__npy_i]; \
                  _PyAIT(it)->coordinates[__npy_i] = \
                          destination[__npy_i]; \
                  _PyAIT(it)->index += destination[__npy_i] * \
                          ( __npy_i==_PyAIT(it)->nd_m1 ? 1 : \
                            _PyAIT(it)->dims_m1[__npy_i+1]+1) ; \
          } \
  } while (0)
  
  #define PyArray_ITER_GOTO1D(it, ind) do { \
          int __npy_i; \
          npy_intp __npy_ind = (npy_intp)(ind); \
          if (__npy_ind < 0) __npy_ind += _PyAIT(it)->size; \
          _PyAIT(it)->index = __npy_ind; \
          if (_PyAIT(it)->nd_m1 == 0) { \
                  _PyAIT(it)->dataptr = PyArray_BYTES(_PyAIT(it)->ao) + \
                          __npy_ind * _PyAIT(it)->strides[0]; \
          } \
          else if (_PyAIT(it)->contiguous) \
                  _PyAIT(it)->dataptr = PyArray_BYTES(_PyAIT(it)->ao) + \
                          __npy_ind * PyArray_DESCR(_PyAIT(it)->ao)->elsize; \
          else { \
                  _PyAIT(it)->dataptr = PyArray_BYTES(_PyAIT(it)->ao); \
                  for (__npy_i = 0; __npy_i<=_PyAIT(it)->nd_m1; \
                       __npy_i++) { \
                          _PyAIT(it)->dataptr += \
                                  (__npy_ind / _PyAIT(it)->factors[__npy_i]) \
                                  * _PyAIT(it)->strides[__npy_i]; \
                          __npy_ind %= _PyAIT(it)->factors[__npy_i]; \
                  } \
          } \
  } while (0)
  
  #define PyArray_ITER_DATA(it) ((void *)(_PyAIT(it)->dataptr))
  
  #define PyArray_ITER_NOTDONE(it) (_PyAIT(it)->index < _PyAIT(it)->size)
  
  
  /*
   * Any object passed to PyArray_Broadcast must be binary compatible
   * with this structure.
   */
  
  typedef struct {
          PyObject_HEAD
          int                  numiter;                 /* number of iters */
          npy_intp             size;                    /* broadcasted size */
          npy_intp             index;                   /* current index */
          int                  nd;                      /* number of dims */
          npy_intp             dimensions[NPY_MAXDIMS]; /* dimensions */
          PyArrayIterObject    *iters[NPY_MAXARGS];     /* iterators */
  } PyArrayMultiIterObject;
  
  #define _PyMIT(m) ((PyArrayMultiIterObject *)(m))
  #define PyArray_MultiIter_RESET(multi) do {                                   \
          int __npy_mi;                                                         \
          _PyMIT(multi)->index = 0;                                             \
          for (__npy_mi=0; __npy_mi < _PyMIT(multi)->numiter;  __npy_mi++) {    \
                  PyArray_ITER_RESET(_PyMIT(multi)->iters[__npy_mi]);           \
          }                                                                     \
  } while (0)
  
  #define PyArray_MultiIter_NEXT(multi) do {                                    \
          int __npy_mi;                                                         \
          _PyMIT(multi)->index++;                                               \
          for (__npy_mi=0; __npy_mi < _PyMIT(multi)->numiter;   __npy_mi++) {   \
                  PyArray_ITER_NEXT(_PyMIT(multi)->iters[__npy_mi]);            \
          }                                                                     \
  } while (0)
  
  #define PyArray_MultiIter_GOTO(multi, dest) do {                            \
          int __npy_mi;                                                       \
          for (__npy_mi=0; __npy_mi < _PyMIT(multi)->numiter; __npy_mi++) {   \
                  PyArray_ITER_GOTO(_PyMIT(multi)->iters[__npy_mi], dest);    \
          }                                                                   \
          _PyMIT(multi)->index = _PyMIT(multi)->iters[0]->index;              \
  } while (0)
  
  #define PyArray_MultiIter_GOTO1D(multi, ind) do {                          \
          int __npy_mi;                                                      \
          for (__npy_mi=0; __npy_mi < _PyMIT(multi)->numiter; __npy_mi++) {  \
                  PyArray_ITER_GOTO1D(_PyMIT(multi)->iters[__npy_mi], ind);  \
          }                                                                  \
          _PyMIT(multi)->index = _PyMIT(multi)->iters[0]->index;             \
  } while (0)
  
  #define PyArray_MultiIter_DATA(multi, i)                \
          ((void *)(_PyMIT(multi)->iters[i]->dataptr))
  
  #define PyArray_MultiIter_NEXTi(multi, i)               \
          PyArray_ITER_NEXT(_PyMIT(multi)->iters[i])
  
  #define PyArray_MultiIter_NOTDONE(multi)                \
          (_PyMIT(multi)->index < _PyMIT(multi)->size)
  
  
  /*
   * Store the information needed for fancy-indexing over an array. The
   * fields are slightly unordered to keep consec, dataptr and subspace
   * where they were originally.
   */
  typedef struct {
          PyObject_HEAD
          /*
           * Multi-iterator portion --- needs to be present in this
           * order to work with PyArray_Broadcast
           */
  
          int                   numiter;                 /* number of index-array
                                                            iterators */
          npy_intp              size;                    /* size of broadcasted
                                                            result */
          npy_intp              index;                   /* current index */
          int                   nd;                      /* number of dims */
          npy_intp              dimensions[NPY_MAXDIMS]; /* dimensions */
          NpyIter               *outer;                  /* index objects
                                                            iterator */
          void                  *unused[NPY_MAXDIMS - 2];
          PyArrayObject         *array;
          /* Flat iterator for the indexed array. For compatibility solely. */
          PyArrayIterObject     *ait;
  
          /*
           * Subspace array. For binary compatibility (was an iterator,
           * but only the check for NULL should be used).
           */
          PyArrayObject         *subspace;
  
          /*
           * if subspace iteration, then this is the array of axes in
           * the underlying array represented by the index objects
           */
          int                   iteraxes[NPY_MAXDIMS];
          npy_intp              fancy_strides[NPY_MAXDIMS];
  
          /* pointer when all fancy indices are 0 */
          char                  *baseoffset;
  
          /*
           * after binding consec denotes at which axis the fancy axes
           * are inserted.
           */
          int                   consec;
          char                  *dataptr;
  
          int                   nd_fancy;
          npy_intp              fancy_dims[NPY_MAXDIMS];
  
          /* Whether the iterator (any of the iterators) requires API */
          int                   needs_api;
  
          /*
           * Extra op information.
           */
          PyArrayObject         *extra_op;
          PyArray_Descr         *extra_op_dtype;         /* desired dtype */
          npy_uint32            *extra_op_flags;         /* Iterator flags */
  
          NpyIter               *extra_op_iter;
          NpyIter_IterNextFunc  *extra_op_next;
          char                  **extra_op_ptrs;
  
          /*
           * Information about the iteration state.
           */
          NpyIter_IterNextFunc  *outer_next;
          char                  **outer_ptrs;
          npy_intp              *outer_strides;
  
          /*
           * Information about the subspace iterator.
           */
          NpyIter               *subspace_iter;
          NpyIter_IterNextFunc  *subspace_next;
          char                  **subspace_ptrs;
          npy_intp              *subspace_strides;
  
          /* Count for the external loop (which ever it is) for API iteration */
          npy_intp              iter_count;
  
  } PyArrayMapIterObject;
  
  enum {
      NPY_NEIGHBORHOOD_ITER_ZERO_PADDING,
      NPY_NEIGHBORHOOD_ITER_ONE_PADDING,
      NPY_NEIGHBORHOOD_ITER_CONSTANT_PADDING,
      NPY_NEIGHBORHOOD_ITER_CIRCULAR_PADDING,
      NPY_NEIGHBORHOOD_ITER_MIRROR_PADDING
  };
  
  typedef struct {
      PyObject_HEAD
  
      /*
       * PyArrayIterObject part: keep this in this exact order
       */
      int               nd_m1;            /* number of dimensions - 1 */
      npy_intp          index, size;
      npy_intp          coordinates[NPY_MAXDIMS];/* N-dimensional loop */
      npy_intp          dims_m1[NPY_MAXDIMS];    /* ao->dimensions - 1 */
      npy_intp          strides[NPY_MAXDIMS];    /* ao->strides or fake */
      npy_intp          backstrides[NPY_MAXDIMS];/* how far to jump back */
      npy_intp          factors[NPY_MAXDIMS];     /* shape factors */
      PyArrayObject     *ao;
      char              *dataptr;        /* pointer to current item*/
      npy_bool          contiguous;
  
      npy_intp          bounds[NPY_MAXDIMS][2];
      npy_intp          limits[NPY_MAXDIMS][2];
      npy_intp          limits_sizes[NPY_MAXDIMS];
      npy_iter_get_dataptr_t translate;
  
      /*
       * New members
       */
      npy_intp nd;
  
      /* Dimensions is the dimension of the array */
      npy_intp dimensions[NPY_MAXDIMS];
  
      /*
       * Neighborhood points coordinates are computed relatively to the
       * point pointed by _internal_iter
       */
      PyArrayIterObject* _internal_iter;
      /*
       * To keep a reference to the representation of the constant value
       * for constant padding
       */
      char* constant;
  
      int mode;
  } PyArrayNeighborhoodIterObject;
  
  /*
   * Neighborhood iterator API
   */
  
  /* General: those work for any mode */
  static NPY_INLINE int
  PyArrayNeighborhoodIter_Reset(PyArrayNeighborhoodIterObject* iter);
  static NPY_INLINE int
  PyArrayNeighborhoodIter_Next(PyArrayNeighborhoodIterObject* iter);
  #if 0
  static NPY_INLINE int
  PyArrayNeighborhoodIter_Next2D(PyArrayNeighborhoodIterObject* iter);
  #endif
  
  /*
   * Include inline implementations - functions defined there are not
   * considered public API
   */
  #define _NPY_INCLUDE_NEIGHBORHOOD_IMP
  #include "_neighborhood_iterator_imp.h"
  #undef _NPY_INCLUDE_NEIGHBORHOOD_IMP
  
  /* The default array type */
  #define NPY_DEFAULT_TYPE NPY_DOUBLE
  
  /*
   * All sorts of useful ways to look into a PyArrayObject. It is recommended
   * to use PyArrayObject * objects instead of always casting from PyObject *,
   * for improved type checking.
   *
   * In many cases here the macro versions of the accessors are deprecated,
   * but can't be immediately changed to inline functions because the
   * preexisting macros accept PyObject * and do automatic casts. Inline
   * functions accepting PyArrayObject * provides for some compile-time
   * checking of correctness when working with these objects in C.
   */
  
  #define PyArray_ISONESEGMENT(m) (PyArray_CHKFLAGS(m, NPY_ARRAY_C_CONTIGUOUS) || \
                                   PyArray_CHKFLAGS(m, NPY_ARRAY_F_CONTIGUOUS))
  
  #define PyArray_ISFORTRAN(m) (PyArray_CHKFLAGS(m, NPY_ARRAY_F_CONTIGUOUS) && \
                               (!PyArray_CHKFLAGS(m, NPY_ARRAY_C_CONTIGUOUS)))
  
  #define PyArray_FORTRAN_IF(m) ((PyArray_CHKFLAGS(m, NPY_ARRAY_F_CONTIGUOUS) ? \
                                 NPY_ARRAY_F_CONTIGUOUS : 0))
  
  #if (defined(NPY_NO_DEPRECATED_API) && (NPY_1_7_API_VERSION <= NPY_NO_DEPRECATED_API))
  /*
   * Changing access macros into functions, to allow for future hiding
   * of the internal memory layout. This later hiding will allow the 2.x series
   * to change the internal representation of arrays without affecting
   * ABI compatibility.
   */
  
  static NPY_INLINE int
  PyArray_NDIM(const PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->nd;
  }
  
  static NPY_INLINE void *
  PyArray_DATA(PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->data;
  }
  
  static NPY_INLINE char *
  PyArray_BYTES(PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->data;
  }
  
  static NPY_INLINE npy_intp *
  PyArray_DIMS(PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->dimensions;
  }
  
  static NPY_INLINE npy_intp *
  PyArray_STRIDES(PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->strides;
  }
  
  static NPY_INLINE npy_intp
  PyArray_DIM(const PyArrayObject *arr, int idim)
  {
      return ((PyArrayObject_fields *)arr)->dimensions[idim];
  }
  
  static NPY_INLINE npy_intp
  PyArray_STRIDE(const PyArrayObject *arr, int istride)
  {
      return ((PyArrayObject_fields *)arr)->strides[istride];
  }
  
  static NPY_INLINE NPY_RETURNS_BORROWED_REF PyObject *
  PyArray_BASE(PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->base;
  }
  
  static NPY_INLINE NPY_RETURNS_BORROWED_REF PyArray_Descr *
  PyArray_DESCR(PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->descr;
  }
  
  static NPY_INLINE int
  PyArray_FLAGS(const PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->flags;
  }
  
  static NPY_INLINE npy_intp
  PyArray_ITEMSIZE(const PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->descr->elsize;
  }
  
  static NPY_INLINE int
  PyArray_TYPE(const PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->descr->type_num;
  }
  
  static NPY_INLINE int
  PyArray_CHKFLAGS(const PyArrayObject *arr, int flags)
  {
      return (PyArray_FLAGS(arr) & flags) == flags;
  }
  
  static NPY_INLINE PyObject *
  PyArray_GETITEM(const PyArrayObject *arr, const char *itemptr)
  {
      return ((PyArrayObject_fields *)arr)->descr->f->getitem(
                                          (void *)itemptr, (PyArrayObject *)arr);
  }
  
  /*
   * SETITEM should only be used if it is known that the value is a scalar
   * and of a type understood by the arrays dtype.
   * Use `PyArray_Pack` if the value may be of a different dtype.
   */
  static NPY_INLINE int
  PyArray_SETITEM(PyArrayObject *arr, char *itemptr, PyObject *v)
  {
      return ((PyArrayObject_fields *)arr)->descr->f->setitem(v, itemptr, arr);
  }
  
  #else
  
  /* These macros are deprecated as of NumPy 1.7. */
  #define PyArray_NDIM(obj) (((PyArrayObject_fields *)(obj))->nd)
  #define PyArray_BYTES(obj) (((PyArrayObject_fields *)(obj))->data)
  #define PyArray_DATA(obj) ((void *)((PyArrayObject_fields *)(obj))->data)
  #define PyArray_DIMS(obj) (((PyArrayObject_fields *)(obj))->dimensions)
  #define PyArray_STRIDES(obj) (((PyArrayObject_fields *)(obj))->strides)
  #define PyArray_DIM(obj,n) (PyArray_DIMS(obj)[n])
  #define PyArray_STRIDE(obj,n) (PyArray_STRIDES(obj)[n])
  #define PyArray_BASE(obj) (((PyArrayObject_fields *)(obj))->base)
  #define PyArray_DESCR(obj) (((PyArrayObject_fields *)(obj))->descr)
  #define PyArray_FLAGS(obj) (((PyArrayObject_fields *)(obj))->flags)
  #define PyArray_CHKFLAGS(m, FLAGS) \
          ((((PyArrayObject_fields *)(m))->flags & (FLAGS)) == (FLAGS))
  #define PyArray_ITEMSIZE(obj) \
                      (((PyArrayObject_fields *)(obj))->descr->elsize)
  #define PyArray_TYPE(obj) \
                      (((PyArrayObject_fields *)(obj))->descr->type_num)
  #define PyArray_GETITEM(obj,itemptr) \
          PyArray_DESCR(obj)->f->getitem((char *)(itemptr), \
                                       (PyArrayObject *)(obj))
  
  #define PyArray_SETITEM(obj,itemptr,v) \
          PyArray_DESCR(obj)->f->setitem((PyObject *)(v), \
                                       (char *)(itemptr), \
                                       (PyArrayObject *)(obj))
  #endif
  
  static NPY_INLINE PyArray_Descr *
  PyArray_DTYPE(PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->descr;
  }
  
  static NPY_INLINE npy_intp *
  PyArray_SHAPE(PyArrayObject *arr)
  {
      return ((PyArrayObject_fields *)arr)->dimensions;
  }
  
  /*
   * Enables the specified array flags. Does no checking,
   * assumes you know what you're doing.
   */
  static NPY_INLINE void
  PyArray_ENABLEFLAGS(PyArrayObject *arr, int flags)
  {
      ((PyArrayObject_fields *)arr)->flags |= flags;
  }
  
  /*
   * Clears the specified array flags. Does no checking,
   * assumes you know what you're doing.
   */
  static NPY_INLINE void
  PyArray_CLEARFLAGS(PyArrayObject *arr, int flags)
  {
      ((PyArrayObject_fields *)arr)->flags &= ~flags;
  }
  
  #define PyTypeNum_ISBOOL(type) ((type) == NPY_BOOL)
  
  #define PyTypeNum_ISUNSIGNED(type) (((type) == NPY_UBYTE) ||   \
                                   ((type) == NPY_USHORT) ||     \
                                   ((type) == NPY_UINT) ||       \
                                   ((type) == NPY_ULONG) ||      \
                                   ((type) == NPY_ULONGLONG))
  
  #define PyTypeNum_ISSIGNED(type) (((type) == NPY_BYTE) ||      \
                                 ((type) == NPY_SHORT) ||        \
                                 ((type) == NPY_INT) ||          \
                                 ((type) == NPY_LONG) ||         \
                                 ((type) == NPY_LONGLONG))
  
  #define PyTypeNum_ISINTEGER(type) (((type) >= NPY_BYTE) &&     \
                                  ((type) <= NPY_ULONGLONG))
  
  #define PyTypeNum_ISFLOAT(type) ((((type) >= NPY_FLOAT) && \
                                ((type) <= NPY_LONGDOUBLE)) || \
                                ((type) == NPY_HALF))
  
  #define PyTypeNum_ISNUMBER(type) (((type) <= NPY_CLONGDOUBLE) || \
                                    ((type) == NPY_HALF))
  
  #define PyTypeNum_ISSTRING(type) (((type) == NPY_STRING) ||    \
                                    ((type) == NPY_UNICODE))
  
  #define PyTypeNum_ISCOMPLEX(type) (((type) >= NPY_CFLOAT) &&   \
                                  ((type) <= NPY_CLONGDOUBLE))
  
  #define PyTypeNum_ISPYTHON(type) (((type) == NPY_LONG) ||      \
                                    ((type) == NPY_DOUBLE) ||    \
                                    ((type) == NPY_CDOUBLE) ||   \
                                    ((type) == NPY_BOOL) ||      \
                                    ((type) == NPY_OBJECT ))
  
  #define PyTypeNum_ISFLEXIBLE(type) (((type) >=NPY_STRING) &&  \
                                      ((type) <=NPY_VOID))
  
  #define PyTypeNum_ISDATETIME(type) (((type) >=NPY_DATETIME) &&  \
                                      ((type) <=NPY_TIMEDELTA))
  
  #define PyTypeNum_ISUSERDEF(type) (((type) >= NPY_USERDEF) && \
                                     ((type) < NPY_USERDEF+     \
                                      NPY_NUMUSERTYPES))
  
  #define PyTypeNum_ISEXTENDED(type) (PyTypeNum_ISFLEXIBLE(type) ||  \
                                      PyTypeNum_ISUSERDEF(type))
  
  #define PyTypeNum_ISOBJECT(type) ((type) == NPY_OBJECT)
  
  
  #define PyDataType_ISBOOL(obj) PyTypeNum_ISBOOL(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISUNSIGNED(obj) PyTypeNum_ISUNSIGNED(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISSIGNED(obj) PyTypeNum_ISSIGNED(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISINTEGER(obj) PyTypeNum_ISINTEGER(((PyArray_Descr*)(obj))->type_num )
  #define PyDataType_ISFLOAT(obj) PyTypeNum_ISFLOAT(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISNUMBER(obj) PyTypeNum_ISNUMBER(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISSTRING(obj) PyTypeNum_ISSTRING(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISCOMPLEX(obj) PyTypeNum_ISCOMPLEX(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISPYTHON(obj) PyTypeNum_ISPYTHON(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISFLEXIBLE(obj) PyTypeNum_ISFLEXIBLE(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISDATETIME(obj) PyTypeNum_ISDATETIME(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISUSERDEF(obj) PyTypeNum_ISUSERDEF(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISEXTENDED(obj) PyTypeNum_ISEXTENDED(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_ISOBJECT(obj) PyTypeNum_ISOBJECT(((PyArray_Descr*)(obj))->type_num)
  #define PyDataType_HASFIELDS(obj) (((PyArray_Descr *)(obj))->names != NULL)
  #define PyDataType_HASSUBARRAY(dtype) ((dtype)->subarray != NULL)
  #define PyDataType_ISUNSIZED(dtype) ((dtype)->elsize == 0 && \
                                        !PyDataType_HASFIELDS(dtype))
  #define PyDataType_MAKEUNSIZED(dtype) ((dtype)->elsize = 0)
  
  #define PyArray_ISBOOL(obj) PyTypeNum_ISBOOL(PyArray_TYPE(obj))
  #define PyArray_ISUNSIGNED(obj) PyTypeNum_ISUNSIGNED(PyArray_TYPE(obj))
  #define PyArray_ISSIGNED(obj) PyTypeNum_ISSIGNED(PyArray_TYPE(obj))
  #define PyArray_ISINTEGER(obj) PyTypeNum_ISINTEGER(PyArray_TYPE(obj))
  #define PyArray_ISFLOAT(obj) PyTypeNum_ISFLOAT(PyArray_TYPE(obj))
  #define PyArray_ISNUMBER(obj) PyTypeNum_ISNUMBER(PyArray_TYPE(obj))
  #define PyArray_ISSTRING(obj) PyTypeNum_ISSTRING(PyArray_TYPE(obj))
  #define PyArray_ISCOMPLEX(obj) PyTypeNum_ISCOMPLEX(PyArray_TYPE(obj))
  #define PyArray_ISPYTHON(obj) PyTypeNum_ISPYTHON(PyArray_TYPE(obj))
  #define PyArray_ISFLEXIBLE(obj) PyTypeNum_ISFLEXIBLE(PyArray_TYPE(obj))
  #define PyArray_ISDATETIME(obj) PyTypeNum_ISDATETIME(PyArray_TYPE(obj))
  #define PyArray_ISUSERDEF(obj) PyTypeNum_ISUSERDEF(PyArray_TYPE(obj))
  #define PyArray_ISEXTENDED(obj) PyTypeNum_ISEXTENDED(PyArray_TYPE(obj))
  #define PyArray_ISOBJECT(obj) PyTypeNum_ISOBJECT(PyArray_TYPE(obj))
  #define PyArray_HASFIELDS(obj) PyDataType_HASFIELDS(PyArray_DESCR(obj))
  
      /*
       * FIXME: This should check for a flag on the data-type that
       * states whether or not it is variable length.  Because the
       * ISFLEXIBLE check is hard-coded to the built-in data-types.
       */
  #define PyArray_ISVARIABLE(obj) PyTypeNum_ISFLEXIBLE(PyArray_TYPE(obj))
  
  #define PyArray_SAFEALIGNEDCOPY(obj) (PyArray_ISALIGNED(obj) && !PyArray_ISVARIABLE(obj))
  
  
  #define NPY_LITTLE '<'
  #define NPY_BIG '>'
  #define NPY_NATIVE '='
  #define NPY_SWAP 's'
  #define NPY_IGNORE '|'
  
  #if NPY_BYTE_ORDER == NPY_BIG_ENDIAN
  #define NPY_NATBYTE NPY_BIG
  #define NPY_OPPBYTE NPY_LITTLE
  #else
  #define NPY_NATBYTE NPY_LITTLE
  #define NPY_OPPBYTE NPY_BIG
  #endif
  
  #define PyArray_ISNBO(arg) ((arg) != NPY_OPPBYTE)
  #define PyArray_IsNativeByteOrder PyArray_ISNBO
  #define PyArray_ISNOTSWAPPED(m) PyArray_ISNBO(PyArray_DESCR(m)->byteorder)
  #define PyArray_ISBYTESWAPPED(m) (!PyArray_ISNOTSWAPPED(m))
  
  #define PyArray_FLAGSWAP(m, flags) (PyArray_CHKFLAGS(m, flags) &&       \
                                      PyArray_ISNOTSWAPPED(m))
  
  #define PyArray_ISCARRAY(m) PyArray_FLAGSWAP(m, NPY_ARRAY_CARRAY)
  #define PyArray_ISCARRAY_RO(m) PyArray_FLAGSWAP(m, NPY_ARRAY_CARRAY_RO)
  #define PyArray_ISFARRAY(m) PyArray_FLAGSWAP(m, NPY_ARRAY_FARRAY)
  #define PyArray_ISFARRAY_RO(m) PyArray_FLAGSWAP(m, NPY_ARRAY_FARRAY_RO)
  #define PyArray_ISBEHAVED(m) PyArray_FLAGSWAP(m, NPY_ARRAY_BEHAVED)
  #define PyArray_ISBEHAVED_RO(m) PyArray_FLAGSWAP(m, NPY_ARRAY_ALIGNED)
  
  
  #define PyDataType_ISNOTSWAPPED(d) PyArray_ISNBO(((PyArray_Descr *)(d))->byteorder)
  #define PyDataType_ISBYTESWAPPED(d) (!PyDataType_ISNOTSWAPPED(d))
  
  /************************************************************
   * A struct used by PyArray_CreateSortedStridePerm, new in 1.7.
   ************************************************************/
  
  typedef struct {
      npy_intp perm, stride;
  } npy_stride_sort_item;
  
  /************************************************************
   * This is the form of the struct that's stored in the
   * PyCapsule returned by an array's __array_struct__ attribute. See
   * https://docs.scipy.org/doc/numpy/reference/arrays.interface.html for the full
   * documentation.
   ************************************************************/
  typedef struct {
      int two;              /*
                             * contains the integer 2 as a sanity
                             * check
                             */
  
      int nd;               /* number of dimensions */
  
      char typekind;        /*
                             * kind in array --- character code of
                             * typestr
                             */
  
      int itemsize;         /* size of each element */
  
      int flags;            /*
                             * how should be data interpreted. Valid
                             * flags are CONTIGUOUS (1), F_CONTIGUOUS (2),
                             * ALIGNED (0x100), NOTSWAPPED (0x200), and
                             * WRITEABLE (0x400).  ARR_HAS_DESCR (0x800)
                             * states that arrdescr field is present in
                             * structure
                             */
  
      npy_intp *shape;       /*
                              * A length-nd array of shape
                              * information
                              */
  
      npy_intp *strides;    /* A length-nd array of stride information */
  
      void *data;           /* A pointer to the first element of the array */
  
      PyObject *descr;      /*
                             * A list of fields or NULL (ignored if flags
                             * does not have ARR_HAS_DESCR flag set)
                             */
  } PyArrayInterface;
  
  /*
   * This is a function for hooking into the PyDataMem_NEW/FREE/RENEW functions.
   * See the documentation for PyDataMem_SetEventHook.
   */
  typedef void (PyDataMem_EventHookFunc)(void *inp, void *outp, size_t size,
                                         void *user_data);
  
  
  /*
   * PyArray_DTypeMeta related definitions.
   *
   * As of now, this API is preliminary and will be extended as necessary.
   */
  #if defined(NPY_INTERNAL_BUILD) && NPY_INTERNAL_BUILD
      /*
       * The Structures defined in this block are considered private API and
       * may change without warning!
       */
      /* TODO: Make this definition public in the API, as soon as its settled */
      NPY_NO_EXPORT extern PyTypeObject PyArrayDTypeMeta_Type;
  
      typedef struct PyArray_DTypeMeta_tag PyArray_DTypeMeta;
  
      typedef PyArray_Descr *(discover_descr_from_pyobject_function)(
              PyArray_DTypeMeta *cls, PyObject *obj);
  
      /*
       * Before making this public, we should decide whether it should pass
       * the type, or allow looking at the object. A possible use-case:
       * `np.array(np.array([0]), dtype=np.ndarray)`
       * Could consider arrays that are not `dtype=ndarray` "scalars".
       */
      typedef int (is_known_scalar_type_function)(
              PyArray_DTypeMeta *cls, PyTypeObject *obj);
  
      typedef PyArray_Descr *(default_descr_function)(PyArray_DTypeMeta *cls);
      typedef PyArray_DTypeMeta *(common_dtype_function)(
              PyArray_DTypeMeta *dtype1, PyArray_DTypeMeta *dtyep2);
      typedef PyArray_Descr *(common_instance_function)(
              PyArray_Descr *dtype1, PyArray_Descr *dtyep2);
  
      /*
       * While NumPy DTypes would not need to be heap types the plan is to
       * make DTypes available in Python at which point they will be heap types.
       * Since we also wish to add fields to the DType class, this looks like
       * a typical instance definition, but with PyHeapTypeObject instead of
       * only the PyObject_HEAD.
       * This must only be exposed very extremely careful consideration, since
       * it is a fairly complex construct which may be better to allow
       * refactoring of.
       */
      struct PyArray_DTypeMeta_tag {
          PyHeapTypeObject super;
  
          /*
           * Most DTypes will have a singleton default instance, for the
           * parametric legacy DTypes (bytes, string, void, datetime) this
           * may be a pointer to the *prototype* instance?
           */
          PyArray_Descr *singleton;
          /*
           * Is this DType created using the old API? This exists mainly to
           * allow for assertions in paths specific to wrapping legacy types.
           */
          npy_bool legacy;
          /* The values stored by a parametric datatype depend on its instance */
          npy_bool parametric;
          /* whether the DType can be instantiated (i.e. np.dtype cannot) */
          npy_bool abstract;
  
          /*
           * The following fields replicate the most important dtype information.
           * In the legacy implementation most of these are stored in the
           * PyArray_Descr struct.
           */
          /* The type object of the scalar instances (may be NULL?) */
          PyTypeObject *scalar_type;
          /* kind for this type */
          char kind;
          /* unique-character representing this type */
          char type;
          /* flags describing data type */
          char flags;
          /* number representing this type */
          int type_num;
          /*
           * Point to the original ArrFuncs.
           * NOTE: We could make a copy to detect changes to `f`.
           */
          PyArray_ArrFuncs *f;
  
          /* DType methods, these could be moved into its own struct */
          discover_descr_from_pyobject_function *discover_descr_from_pyobject;
          is_known_scalar_type_function *is_known_scalar_type;
          default_descr_function *default_descr;
          common_dtype_function *common_dtype;
          common_instance_function *common_instance;
          /*
           * Dictionary of ArrayMethods representing most possible casts
           * (structured and object are exceptions).
           * This should potentially become a weak mapping in the future.
           */
          PyObject *castingimpls;
      };
  
  #endif  /* NPY_INTERNAL_BUILD */
  
  
  /*
   * Use the keyword NPY_DEPRECATED_INCLUDES to ensure that the header files
   * npy_*_*_deprecated_api.h are only included from here and nowhere else.
   */
  #ifdef NPY_DEPRECATED_INCLUDES
  #error "Do not use the reserved keyword NPY_DEPRECATED_INCLUDES."
  #endif
  #define NPY_DEPRECATED_INCLUDES
  #if !defined(NPY_NO_DEPRECATED_API) || \
      (NPY_NO_DEPRECATED_API < NPY_1_7_API_VERSION)
  #include "npy_1_7_deprecated_api.h"
  #endif
  /*
   * There is no file npy_1_8_deprecated_api.h since there are no additional
   * deprecated API features in NumPy 1.8.
   *
   * Note to maintainers: insert code like the following in future NumPy
   * versions.
   *
   * #if !defined(NPY_NO_DEPRECATED_API) || \
   *     (NPY_NO_DEPRECATED_API < NPY_1_9_API_VERSION)
   * #include "npy_1_9_deprecated_api.h"
   * #endif
   */
  #undef NPY_DEPRECATED_INCLUDES
  
  #endif /* NPY_ARRAYTYPES_H */
  