// THIS IS A GENERATED FILE. DO NOT MODIFY WITH CHANGES YOU WANT TO KEEP
// Generated on 2025-09-22 20:14:37 with   nps_pywrap_bindings/mrcal-genpywrap.py


#define FUNCTIONS(_) \
  _(_project, "Internal point-projection routine\n\nThis is the internals for mrcal.project(). As a user, please call THAT function,\nand see the docs for that function. The differences:\n\n- This is just the no-gradients function. The internal function that reports the\n  gradients also is _project_withgrad\n\n- To make the broadcasting work, the argument order in this function is\n  different. numpysane_pywrap broadcasts the leading arguments, so this function\n  takes the lensmodel (the one argument that does not broadcast) last\n\n- To speed things up, this function doesn't call the C mrcal_project(), but uses\n  the _mrcal_project_internal...() functions instead. That allows as much as\n  possible of the outer init stuff to be moved outside of the slice computation\n  loop\n\nThis function is wrapped with numpysane_pywrap, so the points and the intrinsics\nbroadcast as expected\n\nThe outer logic (outside the loop-over-N-points) is duplicated in\nmrcal_project() and in the python wrapper definition in _project() and\n_project_withgrad() in mrcal-genpywrap.py. Please keep them in sync\n\n") \
  _(_project_withgrad, "Internal point-projection routine\n\nThis is the internals for mrcal.project(). As a user, please call THAT function,\nand see the docs for that function. The differences:\n\n- This is just the gradients-returning function. The internal function that\n  skips those is _project\n\n- To make the broadcasting work, the argument order in this function is\n  different. numpysane_pywrap broadcasts the leading arguments, so this function\n  takes the lensmodel (the one argument that does not broadcast) last\n\n- To speed things up, this function doesn't call the C mrcal_project(), but uses\n  the _mrcal_project_internal...() functions instead. That allows as much as\n  possible of the outer init stuff to be moved outside of the slice computation\n  loop\n\nThis function is wrapped with numpysane_pywrap, so the points and the intrinsics\nbroadcast as expected\n\nThe outer logic (outside the loop-over-N-points) is duplicated in\nmrcal_project() and in the python wrapper definition in _project() and\n_project_withgrad() in mrcal-genpywrap.py. Please keep them in sync\n\n") \
  _(_unproject, "Internal point-unprojection routine\n\nThis is the internals for mrcal.unproject(). As a user, please call THAT\nfunction, and see the docs for that function. The differences:\n\n- To make the broadcasting work, the argument order in this function is\n  different. numpysane_pywrap broadcasts the leading arguments, so this function\n  takes the lensmodel (the one argument that does not broadcast) last\n\n- This function requires gradients, so it does not support some lens models;\n  CAHVORE for instance\n\n- To speed things up, this function doesn't call the C mrcal_unproject(), but\n  uses the _mrcal_unproject_internal...() functions instead. That allows as much\n  as possible of the outer init stuff to be moved outside of the slice\n  computation loop\n\nThis function is wrapped with numpysane_pywrap, so the points and the intrinsics\nbroadcast as expected\n\nThe outer logic (outside the loop-over-N-points) is duplicated in\nmrcal_unproject() and in the python wrapper definition in _unproject()\nmrcal-genpywrap.py. Please keep them in sync ") \
  _(_project_pinhole, "Internal projection routine\n\nThis is the internals for mrcal.project_pinhole(). As a user, please call\nTHAT function, and see the docs for that function. The differences:\n\n- This is just the no-gradients function. The internal function that reports the\n  gradients also is _project_pinhole_withgrad()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_project_pinhole_withgrad, "Internal projection routine with gradients\n\nThis is the internals for mrcal.project_pinhole(). As a user, please call\nTHAT function, and see the docs for that function. The differences:\n\n- This is just the gradient-reporting function. The internal function that\n  does not report the gradients is _project_pinhole()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_unproject_pinhole, "Internal unprojection routine\n\nThis is the internals for mrcal.unproject_pinhole(). As a user, please\ncall THAT function, and see the docs for that function. The differences:\n\n- This is just the no-gradients function. The internal function that reports the\n  gradients also is _unproject_pinhole_withgrad()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_unproject_pinhole_withgrad, "Internal unprojection routine\n\nThis is the internals for mrcal.unproject_pinhole(). As a user, please\ncall THAT function, and see the docs for that function. The differences:\n\n- This is just the gradient-reporting function. The internal function that does\n  not report the gradients is _unproject_pinhole()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_project_stereographic, "Internal projection routine\n\nThis is the internals for mrcal.project_stereographic(). As a user, please call\nTHAT function, and see the docs for that function. The differences:\n\n- This is just the no-gradients function. The internal function that reports the\n  gradients also is _project_stereographic_withgrad()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_project_stereographic_withgrad, "Internal projection routine with gradients\n\nThis is the internals for mrcal.project_stereographic(). As a user, please call\nTHAT function, and see the docs for that function. The differences:\n\n- This is just the gradient-reporting function. The internal function that\n  does not report the gradients is _project_stereographic()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_unproject_stereographic, "Internal unprojection routine\n\nThis is the internals for mrcal.unproject_stereographic(). As a user, please\ncall THAT function, and see the docs for that function. The differences:\n\n- This is just the no-gradients function. The internal function that reports the\n  gradients also is _unproject_stereographic_withgrad()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_unproject_stereographic_withgrad, "Internal unprojection routine\n\nThis is the internals for mrcal.unproject_stereographic(). As a user, please\ncall THAT function, and see the docs for that function. The differences:\n\n- This is just the gradient-reporting function. The internal function that does\n  not report the gradients is _unproject_stereographic()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_project_lonlat, "Internal projection routine\n\nThis is the internals for mrcal.project_lonlat(). As a user, please call\nTHAT function, and see the docs for that function. The differences:\n\n- This is just the no-gradients function. The internal function that reports the\n  gradients also is _project_lonlat_withgrad()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_project_lonlat_withgrad, "Internal projection routine with gradients\n\nThis is the internals for mrcal.project_lonlat(). As a user, please call\nTHAT function, and see the docs for that function. The differences:\n\n- This is just the gradient-reporting function. The internal function that\n  does not report the gradients is _project_lonlat()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_unproject_lonlat, "Internal unprojection routine\n\nThis is the internals for mrcal.unproject_lonlat(). As a user, please\ncall THAT function, and see the docs for that function. The differences:\n\n- This is just the no-gradients function. The internal function that reports the\n  gradients also is _unproject_lonlat_withgrad()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_unproject_lonlat_withgrad, "Internal unprojection routine\n\nThis is the internals for mrcal.unproject_lonlat(). As a user, please\ncall THAT function, and see the docs for that function. The differences:\n\n- This is just the gradient-reporting function. The internal function that does\n  not report the gradients is _unproject_lonlat()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_project_latlon, "Internal projection routine\n\nThis is the internals for mrcal.project_latlon(). As a user, please call\nTHAT function, and see the docs for that function. The differences:\n\n- This is just the no-gradients function. The internal function that reports the\n  gradients also is _project_latlon_withgrad()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_project_latlon_withgrad, "Internal projection routine with gradients\n\nThis is the internals for mrcal.project_latlon(). As a user, please call\nTHAT function, and see the docs for that function. The differences:\n\n- This is just the gradient-reporting function. The internal function that\n  does not report the gradients is _project_latlon()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_unproject_latlon, "Internal unprojection routine\n\nThis is the internals for mrcal.unproject_latlon(). As a user, please\ncall THAT function, and see the docs for that function. The differences:\n\n- This is just the no-gradients function. The internal function that reports the\n  gradients also is _unproject_latlon_withgrad()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_unproject_latlon_withgrad, "Internal unprojection routine\n\nThis is the internals for mrcal.unproject_latlon(). As a user, please\ncall THAT function, and see the docs for that function. The differences:\n\n- This is just the gradient-reporting function. The internal function that does\n  not report the gradients is _unproject_latlon()\n\nThis function is wrapped with numpysane_pywrap, so the points broadcast as\nexpected\n\n") \
  _(_A_Jt_J_At, "Computes matmult(A,Jt,J,At) for a sparse J\n\nThis is used in the internals of projection_uncertainty().\n\nA has shape (2,Nstate)\n\nJ has shape (Nmeasurements,Nstate). J is large and sparse\n\nWe use the Nleading_rows_J leading rows of J. This integer is passed-in as an\nargument.\n\nmatmult(A, Jt, J, At) has shape (2,2)\n\nThe input matrices are large, but the result is very small. I can't see a way to\ndo this efficiently in pure Python, so I'm writing this.\n\nJ is sparse, stored by row. This is the scipy.sparse.csr_matrix representation,\nand is also how CHOLMOD stores Jt (CHOLMOD stores by column, so the same data\nlooks like Jt to CHOLMOD). The sparse J is given here as the p,i,x arrays from\nCHOLMOD, equivalent to the indptr,indices,data members of\nscipy.sparse.csr_matrix respectively.\n ") \
  _(_A_Jt_J_At__2, "Computes matmult(A,Jt,J,At) for a sparse J where A.shape=(2,N)\n\nExactly the same as _A_Jt_J_At(), but assumes that A.shape=(2,N) for efficiency.\nSee the docs of _A_Jt_J_At() for details.\n ") \
  _(_Jt_x, "Computes matrix-vector multiplication Jt*xt\n\nSYNOPSIS\n\n    Jt_x = np.zeros( (J.shape[-1],), dtype=float)\n    mrcal._mrcal_npsp._Jt_x(J.indptr,\n                            J.indices,\n                            J.data,\n                            x,\n                            out = Jt_x)\n\nJt is the transpose of a (possibly very large) sparse array and x is a dense\ncolumn vector. We pass in\n\n- J: the sparse array\n- xt: the row vector transpose of x\n\nThe output is a dense row vector, the transpose of the multiplication\n\nJ is sparse, stored by row. This is the scipy.sparse.csr_matrix representation,\nand is also how CHOLMOD stores Jt (CHOLMOD stores by column, so the same data\nlooks like Jt to CHOLMOD). The sparse J is given here as the p,i,x arrays from\nCHOLMOD, equivalent to the indptr,indices,data members of\nscipy.sparse.csr_matrix respectively.\n\nNote: The output array MUST be passed-in because there's no way to know its\nshape beforehand. For the same reason, we cannot verify that its shape is\ncorrect, and the caller MUST do that, or else the program can crash.\n\n") \
  _(apply_homography, "Apply a homogeneous-coordinate homography to a set of 2D points\n\nSYNOPSIS\n\n    print( H.shape )\n    ===> (3,3)\n\n    print( q0.shape )\n    ===> (100, 2)\n\n    q1 = mrcal.apply_homography(H10, q0)\n\n    print( q1.shape )\n    ===> (100, 2)\n\nA homography maps from pixel coordinates observed in one camera to pixel\ncoordinates in another. For points represented in homogeneous coordinates ((k*x,\nk*y, k) to represent a pixel (x,y) for any k) a homography is a linear map H.\nSince homogeneous coordinates are unique only up-to-scale, the homography matrix\nH is also unique up to scale.\n\nIf two pinhole cameras are observing a planar surface, there exists a homography\nthat relates observations of the plane in the two cameras.\n\nThis function supports broadcasting fully.\n\nARGUMENTS\n\n- H: an array of shape (..., 3,3). This is the homography matrix. This is unique\n  up-to-scale, so a homography H is functionally equivalent to k*H for any\n  non-zero scalar k\n\n- q: an array of shape (..., 2). The pixel coordinates we are mapping\n\nRETURNED VALUE\n\nAn array of shape (..., 2) containing the pixels q after the homography was\napplied\n\n    ") \
  _(_stereo_range_sparse, "Internal wrapper of mrcal_stereo_range_sparse()\n")


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_module_header.c
#define NPY_NO_DEPRECATED_API NPY_API_VERSION

#include <stdbool.h>
#include <Python.h>
#include <structmember.h>
#include <numpy/arrayobject.h>
#include <signal.h>

// Python is silly. There's some nuance about signal handling where it sets a
// SIGINT (ctrl-c) handler to just set a flag, and the python layer then reads
// this flag and does the thing. Here I'm running C code, so SIGINT would set a
// flag, but not quit, so I can't interrupt the solver. Thus I reset the SIGINT
// handler to the default, and put it back to the python-specific version when
// I'm done
#define SET_SIGINT() struct sigaction sigaction_old;                    \
do {                                                                    \
    if( 0 != sigaction(SIGINT,                                          \
                       &(struct sigaction){ .sa_handler = SIG_DFL },    \
                       &sigaction_old) )                                \
    {                                                                   \
        PyErr_SetString(PyExc_RuntimeError, "sigaction() failed");      \
        goto done;                                                      \
    }                                                                   \
} while(0)
#define RESET_SIGINT() do {                                             \
    if( 0 != sigaction(SIGINT,                                          \
                       &sigaction_old, NULL ))                          \
        PyErr_SetString(PyExc_RuntimeError, "sigaction-restore failed"); \
} while(0)

static
bool parse_dim_for_one_arg(// input and output

                           // so-far-seen named dimensions. Initially these are
                           // <0 to indicate that they're unknown. As the
                           // broadcasting rules determine the values of these,
                           // the values are stored here (>= 0), and checked for
                           // consistency
                           npy_intp* dims_named,

                           // so-far-seen broadcasted dimensions. Initially
                           // these are 1 to indicate that these are compatible
                           // with anything. As non-1 values are seen, those are
                           // stored here (> 1), and checked for consistency
                           npy_intp* dims_extra,

                           // input
                           const int Ndims_extra,
                           const int Ndims_extra_inputs_only,
                           const char* arg_name,
                           const int Ndims_extra_var,
                           const npy_intp* dims_want, const int Ndims_want,
                           const npy_intp* dims_var,  const int Ndims_var,
                           const bool is_output)
{
    // MAKE SURE THE PROTOTYPE DIMENSIONS MATCH (the trailing dimensions)
    //
    // Loop through the dimensions. Set the dimensionality of any new named
    // argument to whatever the current argument has. Any already-known
    // argument must match
    for( int i_dim=-1;
         i_dim >= -Ndims_want;
         i_dim--)
    {
        int i_dim_want = i_dim + Ndims_want;
        int dim_want   = dims_want[i_dim_want];

        int i_dim_var = i_dim + Ndims_var;
        // if we didn't get enough dimensions, use dim=1
        int dim_var = i_dim_var >= 0 ? dims_var[i_dim_var] : 1;

        if(dim_want < 0)
        {
            // This is a named dimension. These can have any value, but
            // ALL dimensions of the same name must thave the SAME value
            // EVERYWHERE
            if(dims_named[-dim_want-1] < 0)
                dims_named[-dim_want-1] = dim_var;

            dim_want = dims_named[-dim_want-1];
        }

        // The prototype dimension (named or otherwise) now has a numeric
        // value. Make sure it matches what I have
        if(dim_want != dim_var)
        {
            if(dims_want[i_dim_want] < 0)
                PyErr_Format(PyExc_RuntimeError,
                             "Argument '%s': prototype says dimension %d (named dimension %d) has length %d, but got %d",
                             arg_name,
                             i_dim, (int)dims_want[i_dim_want],
                             dim_want,
                             dim_var);
            else
                PyErr_Format(PyExc_RuntimeError,
                             "Argument '%s': prototype says dimension %d has length %d, but got %d",
                             arg_name,
                             i_dim,
                             dim_want,
                             dim_var);
            return false;
        }
    }

    // I now know that this argument matches the prototype. I look at the
    // extra dimensions to broadcast, and make sure they match with the
    // dimensions I saw previously

    // MAKE SURE THE BROADCASTED DIMENSIONS MATCH (the leading dimensions)
    //
    // This argument has Ndims_extra_var dimensions above the prototype (may be
    // <0 if there're implicit leading length-1 dimensions at the start). The
    // current dimensions to broadcast must match

    // outputs may be bigger than the inputs (this will result in multiple
    // identical copies in each slice), but may not be smaller. I check that
    // existing extra dimensions are sufficiently large. And then I check to
    // make sure we have enough extra dimensions
    if(is_output)
    {
        for( int i_dim=-1;
             i_dim >= -Ndims_extra_var;
             i_dim--)
        {
            const int i_dim_var = i_dim - Ndims_want + Ndims_var;
            // if we didn't get enough dimensions, use dim=1
            const int dim_var = i_dim_var >= 0 ? dims_var[i_dim_var] : 1;

            const int i_dim_extra = i_dim + Ndims_extra;

            if(dim_var < dims_extra[i_dim_extra])
            {
                PyErr_Format(PyExc_RuntimeError,
                             "Output '%s' dimension %d (broadcasted dimension %d) too small. Inputs have length %d but this output has length %d",
                             arg_name,
                             i_dim-Ndims_want, i_dim,
                             (int)dims_extra[i_dim_extra],
                             dim_var);
                return false;
            }
        }

        // I look through extra dimensions above what this output has to make
        // sure that the output array is big-enough to hold all the output. I
        // only care about the broadcasted slices defined by the input. Because
        // I don't NEED to store all the duplicates created by the output-only
        // broadcasting
        for( int i_dim=-Ndims_extra_var-1;
             i_dim >= -Ndims_extra_inputs_only;
             i_dim--)
        {
            const int i_dim_extra = i_dim + Ndims_extra;

            // What if this test passes, but a subsequent output increases
            // dims_extra[i_dim_extra] so that this would have failed? That is
            // OK. Extra dimensions in the outputs do not create new and
            // different results, and I don't need to make sure I have room to
            // store duplicates
            if(dims_extra[i_dim_extra] > 1)
            {
                // This dimension was set, but this array has a DIFFERENT value
                PyErr_Format(PyExc_RuntimeError,
                             "Argument '%s' dimension %d (broadcasted dimension %d) is too small: this dimension of this output is too small to hold the broadcasted results of size %d",
                             arg_name,
                             i_dim-Ndims_want, i_dim,
                             (int)dims_extra[i_dim_extra]);
                return false;
            }
        }
    }


    for( int i_dim=-1;
         i_dim >= -Ndims_extra_var;
         i_dim--)
    {
        const int i_dim_var = i_dim - Ndims_want + Ndims_var;
        // if we didn't get enough dimensions, use dim=1
        const int dim_var = i_dim_var >= 0 ? dims_var[i_dim_var] : 1;

        const int i_dim_extra = i_dim + Ndims_extra;


        if (dim_var != 1)
        {
            if(i_dim_extra < 0)
            {
                PyErr_Format(PyExc_RuntimeError,
                             "Argument '%s' dimension %d (broadcasted dimension %d) i_dim_extra<0: %d. This shouldn't happen. There's a bug in the implicit-leading-dimension logic. Please report",
                             arg_name,
                             i_dim-Ndims_want, i_dim,
                             i_dim_extra);
                return false;
            }

            // I have a new value for this dimension
            if( dims_extra[i_dim_extra] == 1)
                // This dimension wasn't set yet; I set it
                dims_extra[i_dim_extra] = dim_var;
            else if(dims_extra[i_dim_extra] != dim_var)
            {
                // This dimension was set, but this array has a DIFFERENT value
                PyErr_Format(PyExc_RuntimeError,
                             "Argument '%s' dimension %d (broadcasted dimension %d) mismatch. Previously saw length %d, but here have length %d",
                             arg_name,
                             i_dim-Ndims_want, i_dim,
                             (int)dims_extra[i_dim_extra],
                             dim_var);
                return false;
            }
        }
    }
    return true;
}


#include "mrcal.h"
#include <float.h>

static
bool validate_lensmodel_un_project(// out; valid if we returned true
                        mrcal_lensmodel_t* lensmodel,

                        // in
                        const char* lensmodel_str,
                        int Nintrinsics_in_arg, bool is_project)
{
    if(lensmodel_str == NULL)
    {
        PyErr_Format(PyExc_RuntimeError,
                     "The 'lensmodel' argument is required");
        return false;
    }

    mrcal_lensmodel_from_name(lensmodel, lensmodel_str);
    if( !mrcal_lensmodel_type_is_valid(lensmodel->type) )
    {
        PyErr_Format(PyExc_RuntimeError,
                     "Couldn't parse 'lensmodel' argument '%s'", lensmodel_str);
        return false;
    }

    mrcal_lensmodel_metadata_t meta = mrcal_lensmodel_metadata(lensmodel);
    if( !is_project && !meta.has_gradients )
    {
        PyErr_Format(PyExc_RuntimeError,
                     "The internal _unproject() routine requires lens models that have gradients implemented. Use the Python mrcal.unproject() as a workaround");
        return false;
    }

    int NlensParams = mrcal_lensmodel_num_params(lensmodel);
    if( NlensParams != Nintrinsics_in_arg )
    {
        PyErr_Format(PyExc_RuntimeError,
                     "Lens model '%s' has %d parameters, but the given array has %d",
                     lensmodel_str, NlensParams, Nintrinsics_in_arg);
        return false;
    }

    return true;
}


///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_module_header.c
///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _project

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__intrinsics(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__intrinsics; i++)                               \
      if(dims_full__intrinsics[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__intrinsics; i--)                       \
      {                                                                 \
          if(strides_slice__intrinsics[i+Ndims_slice__intrinsics] != sizeof_element__intrinsics*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'intrinsics' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__intrinsics[i+Ndims_slice__intrinsics];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__intrinsics()              _CHECK_CONTIGUOUS__intrinsics(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__intrinsics() _CHECK_CONTIGUOUS__intrinsics(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__intrinsics()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__intrinsics()

typedef struct { 
              mrcal_lensmodel_t              lensmodel;
              int                            Nintrinsics;
              mrcal_projection_precomputed_t precomputed;
             } ___project__cookie_t;

static
bool ___project__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__intrinsics __attribute__((unused)),
  const npy_intp* dims_full__intrinsics __attribute__((unused)),
  const npy_intp* strides_full__intrinsics __attribute__((unused)),
  const int Ndims_slice__intrinsics __attribute__((unused)),
  const npy_intp* dims_slice__intrinsics __attribute__((unused)),
  const npy_intp* strides_slice__intrinsics __attribute__((unused)),
  npy_intp sizeof_element__intrinsics __attribute__((unused)),
  void* data__intrinsics __attribute__((unused)),
  const char* lensmodel __attribute__((unused)),
  ___project__cookie_t* cookie __attribute__((unused)))
{

              if( !( validate_lensmodel_un_project(&cookie->lensmodel,
                                        lensmodel, dims_slice__intrinsics[0], true) &&
                     CHECK_CONTIGUOUS_AND_SETERROR_ALL()))
                  return false;

              cookie->Nintrinsics = mrcal_lensmodel_num_params(&cookie->lensmodel);
              _mrcal_precompute_lensmodel_data(&cookie->precomputed, &cookie->lensmodel);
              return true;

}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__intrinsics npy_float64
#define item__intrinsics(__ivar0) (*(ctype__intrinsics*)(data_slice__intrinsics + (__ivar0)*strides_slice__intrinsics[0]))

static
bool ___project__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__intrinsics __attribute__((unused)),
  const npy_intp* dims_full__intrinsics __attribute__((unused)),
  const npy_intp* strides_full__intrinsics __attribute__((unused)),
  const int Ndims_slice__intrinsics __attribute__((unused)),
  const npy_intp* dims_slice__intrinsics __attribute__((unused)),
  const npy_intp* strides_slice__intrinsics __attribute__((unused)),
  npy_intp sizeof_element__intrinsics __attribute__((unused)),
  void* data_slice__intrinsics __attribute__((unused)),
  const char* lensmodel __attribute__((unused)),
  ___project__cookie_t* cookie __attribute__((unused)))
{

                 const int N = 1;

                 if(MRCAL_LENSMODEL_IS_OPENCV(cookie->lensmodel.type) ||
                    cookie->lensmodel.type == MRCAL_LENSMODEL_PINHOLE)
                 {
                     _mrcal_project_internal_opencv(
                                (mrcal_point2_t*)data_slice__output,
                                NULL, NULL,
                                (const mrcal_point3_t*)data_slice__points,
                                N,
                                (const double*)data_slice__intrinsics,
                                cookie->Nintrinsics);
                     return true;
                 }

                 return
                     _mrcal_project_internal((mrcal_point2_t*)data_slice__output,
                                             NULL, NULL,
                                             (const mrcal_point3_t*)data_slice__points,
                                             N,
                                             &cookie->lensmodel,
                                             // core, distortions concatenated
                                             (const double*)data_slice__intrinsics,
                                             cookie->Nintrinsics, &cookie->precomputed);

}
#undef item__output
#undef ctype__output
#undef item__points
#undef ctype__points
#undef item__intrinsics
#undef ctype__intrinsics
#define ARGUMENTS(_) \
  _(points) \
  _(intrinsics)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___project__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___project__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___project(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___project__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___project__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const char* lensmodel __attribute__((unused)),___project__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    const char* lensmodel = NULL;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "lensmodel",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "s" ":bindings_npsp._project",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &lensmodel,
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {3};
    const npy_intp PROTOTYPE_intrinsics[1] = {-1};
    const npy_intp PROTOTYPE_output[1] = {2};
    int Ndims_named = 1;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___project__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___project__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___project__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          lensmodel,cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  lensmodel,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___project__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  lensmodel,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___project__slice failed!");
                goto done;
            }

        } while(__pywrap___project__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__intrinsics
#undef CHECK_CONTIGUOUS__intrinsics
#undef CHECK_CONTIGUOUS_AND_SETERROR__intrinsics

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _project_withgrad

#define _CHECK_CONTIGUOUS__output0(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output0; i++)                               \
      if(dims_full__output0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output0; i--)                       \
      {                                                                 \
          if(strides_slice__output0[i+Ndims_slice__output0] != sizeof_element__output0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output0[i+Ndims_slice__output0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output0()              _CHECK_CONTIGUOUS__output0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output0() _CHECK_CONTIGUOUS__output0(true)


#define _CHECK_CONTIGUOUS__output1(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output1; i++)                               \
      if(dims_full__output1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output1; i--)                       \
      {                                                                 \
          if(strides_slice__output1[i+Ndims_slice__output1] != sizeof_element__output1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output1[i+Ndims_slice__output1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output1()              _CHECK_CONTIGUOUS__output1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output1() _CHECK_CONTIGUOUS__output1(true)


#define _CHECK_CONTIGUOUS__output2(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output2; i++)                               \
      if(dims_full__output2[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output2; i--)                       \
      {                                                                 \
          if(strides_slice__output2[i+Ndims_slice__output2] != sizeof_element__output2*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output2' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output2[i+Ndims_slice__output2];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output2()              _CHECK_CONTIGUOUS__output2(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output2() _CHECK_CONTIGUOUS__output2(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__intrinsics(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__intrinsics; i++)                               \
      if(dims_full__intrinsics[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__intrinsics; i--)                       \
      {                                                                 \
          if(strides_slice__intrinsics[i+Ndims_slice__intrinsics] != sizeof_element__intrinsics*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'intrinsics' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__intrinsics[i+Ndims_slice__intrinsics];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__intrinsics()              _CHECK_CONTIGUOUS__intrinsics(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__intrinsics() _CHECK_CONTIGUOUS__intrinsics(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__output2() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__intrinsics()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__output2() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__intrinsics()

typedef struct { 
              mrcal_lensmodel_t              lensmodel;
              int                            Nintrinsics;
              mrcal_projection_precomputed_t precomputed;
             } ___project_withgrad__cookie_t;

static
bool ___project_withgrad__validate(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data__output1 __attribute__((unused)),
  const int Ndims_full__output2 __attribute__((unused)),
  const npy_intp* dims_full__output2 __attribute__((unused)),
  const npy_intp* strides_full__output2 __attribute__((unused)),
  const int Ndims_slice__output2 __attribute__((unused)),
  const npy_intp* dims_slice__output2 __attribute__((unused)),
  const npy_intp* strides_slice__output2 __attribute__((unused)),
  npy_intp sizeof_element__output2 __attribute__((unused)),
  void* data__output2 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__intrinsics __attribute__((unused)),
  const npy_intp* dims_full__intrinsics __attribute__((unused)),
  const npy_intp* strides_full__intrinsics __attribute__((unused)),
  const int Ndims_slice__intrinsics __attribute__((unused)),
  const npy_intp* dims_slice__intrinsics __attribute__((unused)),
  const npy_intp* strides_slice__intrinsics __attribute__((unused)),
  npy_intp sizeof_element__intrinsics __attribute__((unused)),
  void* data__intrinsics __attribute__((unused)),
  const char* lensmodel __attribute__((unused)),
  ___project_withgrad__cookie_t* cookie __attribute__((unused)))
{

              if( !( validate_lensmodel_un_project(&cookie->lensmodel,
                                        lensmodel, dims_slice__intrinsics[0], true) &&
                     CHECK_CONTIGUOUS_AND_SETERROR_ALL()))
                  return false;

              mrcal_lensmodel_metadata_t meta = mrcal_lensmodel_metadata(&cookie->lensmodel);
              if(!meta.has_gradients)
              {
                  PyErr_Format(PyExc_RuntimeError,
                               "_project(get_gradients=True) requires a lens model that has gradient support");
                  return false;
              }
              cookie->Nintrinsics = mrcal_lensmodel_num_params(&cookie->lensmodel);
              _mrcal_precompute_lensmodel_data(&cookie->precomputed, &cookie->lensmodel);
              return true;

}

#define ctype__output0 npy_float64
#define item__output0(__ivar0) (*(ctype__output0*)(data_slice__output0 + (__ivar0)*strides_slice__output0[0]))
#define ctype__output1 npy_float64
#define item__output1(__ivar0,__ivar1) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]))
#define ctype__output2 npy_float64
#define item__output2(__ivar0,__ivar1) (*(ctype__output2*)(data_slice__output2 + (__ivar0)*strides_slice__output2[0]+ (__ivar1)*strides_slice__output2[1]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__intrinsics npy_float64
#define item__intrinsics(__ivar0) (*(ctype__intrinsics*)(data_slice__intrinsics + (__ivar0)*strides_slice__intrinsics[0]))

static
bool ___project_withgrad__0__slice(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data_slice__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data_slice__output1 __attribute__((unused)),
  const int Ndims_full__output2 __attribute__((unused)),
  const npy_intp* dims_full__output2 __attribute__((unused)),
  const npy_intp* strides_full__output2 __attribute__((unused)),
  const int Ndims_slice__output2 __attribute__((unused)),
  const npy_intp* dims_slice__output2 __attribute__((unused)),
  const npy_intp* strides_slice__output2 __attribute__((unused)),
  npy_intp sizeof_element__output2 __attribute__((unused)),
  void* data_slice__output2 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__intrinsics __attribute__((unused)),
  const npy_intp* dims_full__intrinsics __attribute__((unused)),
  const npy_intp* strides_full__intrinsics __attribute__((unused)),
  const int Ndims_slice__intrinsics __attribute__((unused)),
  const npy_intp* dims_slice__intrinsics __attribute__((unused)),
  const npy_intp* strides_slice__intrinsics __attribute__((unused)),
  npy_intp sizeof_element__intrinsics __attribute__((unused)),
  void* data_slice__intrinsics __attribute__((unused)),
  const char* lensmodel __attribute__((unused)),
  ___project_withgrad__cookie_t* cookie __attribute__((unused)))
{

                 const int N = 1;

                 return
                     _mrcal_project_internal((mrcal_point2_t*)data_slice__output0,
                                             (mrcal_point3_t*)data_slice__output1,
                                             (double*)  data_slice__output2,
                                             (const mrcal_point3_t*)data_slice__points,
                                             N,
                                             &cookie->lensmodel,
                                             // core, distortions concatenated
                                             (const double*)data_slice__intrinsics,
                                             cookie->Nintrinsics, &cookie->precomputed);

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__output2
#undef ctype__output2
#undef item__points
#undef ctype__points
#undef item__intrinsics
#undef ctype__intrinsics
#define ARGUMENTS(_) \
  _(points) \
  _(intrinsics)

#define OUTPUTS(_) \
  _(output0) \
  _(output1) \
  _(output2)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___project_withgrad__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___project_withgrad__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___project_withgrad(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___project_withgrad__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___project_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const char* lensmodel __attribute__((unused)),___project_withgrad__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    const char* lensmodel = NULL;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "lensmodel",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "s" ":bindings_npsp._project_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &lensmodel,
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {3};
    const npy_intp PROTOTYPE_intrinsics[1] = {-1};
    const npy_intp PROTOTYPE_output0[1] = {2};
    const npy_intp PROTOTYPE_output1[2] = {2,3};
    const npy_intp PROTOTYPE_output2[2] = {2,-1};
    int Ndims_named = 1;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        __py__output__arg = PyTuple_New(3);
        if(__py__output__arg == NULL)
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Could not allocate output tuple of length %d", 3);
            goto done;
        }

        // I made a tuple, but I don't yet have arrays to populate it with. I'll make
        // those later, and I'll fill the tuple later
        populate_output_tuple__i = 0;
    }
    else
    {
        Py_INCREF(__py__output__arg);

        if( !PySequence_Check(__py__output__arg) )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a non-sequence was given",
                         3);
            goto done;
        }
        if( PySequence_Size(__py__output__arg) != 3 )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a sequence of length %d was given",
                         3, PySequence_Size(__py__output__arg));
            goto done;
        }

#define PULL_OUT_OUTPUT_ARRAYS(name)                                                                         \
        __py__ ## name = (PyArrayObject*)PySequence_GetItem(__py__output__arg, i++);                         \
        if(__py__ ## name == NULL || !PyArray_Check(__py__ ## name))                                         \
        {                                                                                                    \
            PyErr_SetString(PyExc_RuntimeError,                                                              \
                            "Have multiple outputs. The given 'out' array MUST contain pre-allocated arrays, but " #name " is not an array"); \
            goto done;                                                                                       \
        }
        int i=0;
        OUTPUTS(PULL_OUT_OUTPUT_ARRAYS)
#undef PULL_OUT_OUTPUT_ARRAYS
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3,t4, i)                   \
        else if( __pywrap___project_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,t4,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___project_withgrad__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64,float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64,float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___project_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          lensmodel,cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  lensmodel,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___project_withgrad__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  lensmodel,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___project_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___project_withgrad__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS_AND_SETERROR__output0
#undef _CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS_AND_SETERROR__output1
#undef _CHECK_CONTIGUOUS__output2
#undef CHECK_CONTIGUOUS__output2
#undef CHECK_CONTIGUOUS_AND_SETERROR__output2
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__intrinsics
#undef CHECK_CONTIGUOUS__intrinsics
#undef CHECK_CONTIGUOUS_AND_SETERROR__intrinsics

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _unproject

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__intrinsics(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__intrinsics; i++)                               \
      if(dims_full__intrinsics[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__intrinsics; i--)                       \
      {                                                                 \
          if(strides_slice__intrinsics[i+Ndims_slice__intrinsics] != sizeof_element__intrinsics*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'intrinsics' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__intrinsics[i+Ndims_slice__intrinsics];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__intrinsics()              _CHECK_CONTIGUOUS__intrinsics(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__intrinsics() _CHECK_CONTIGUOUS__intrinsics(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__intrinsics()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__intrinsics()

typedef struct { 
              mrcal_lensmodel_t lensmodel;
              mrcal_projection_precomputed_t precomputed;
             } ___unproject__cookie_t;

static
bool ___unproject__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__intrinsics __attribute__((unused)),
  const npy_intp* dims_full__intrinsics __attribute__((unused)),
  const npy_intp* strides_full__intrinsics __attribute__((unused)),
  const int Ndims_slice__intrinsics __attribute__((unused)),
  const npy_intp* dims_slice__intrinsics __attribute__((unused)),
  const npy_intp* strides_slice__intrinsics __attribute__((unused)),
  npy_intp sizeof_element__intrinsics __attribute__((unused)),
  void* data__intrinsics __attribute__((unused)),
  const char* lensmodel __attribute__((unused)),
  ___unproject__cookie_t* cookie __attribute__((unused)))
{

              if( !( validate_lensmodel_un_project(&cookie->lensmodel,
                                        lensmodel, dims_slice__intrinsics[0], false) &&
                     CHECK_CONTIGUOUS_AND_SETERROR_ALL()))
                  return false;

              _mrcal_precompute_lensmodel_data(&cookie->precomputed, &cookie->lensmodel);
              return true;

}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__intrinsics npy_float64
#define item__intrinsics(__ivar0) (*(ctype__intrinsics*)(data_slice__intrinsics + (__ivar0)*strides_slice__intrinsics[0]))

static
bool ___unproject__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__intrinsics __attribute__((unused)),
  const npy_intp* dims_full__intrinsics __attribute__((unused)),
  const npy_intp* strides_full__intrinsics __attribute__((unused)),
  const int Ndims_slice__intrinsics __attribute__((unused)),
  const npy_intp* dims_slice__intrinsics __attribute__((unused)),
  const npy_intp* strides_slice__intrinsics __attribute__((unused)),
  npy_intp sizeof_element__intrinsics __attribute__((unused)),
  void* data_slice__intrinsics __attribute__((unused)),
  const char* lensmodel __attribute__((unused)),
  ___unproject__cookie_t* cookie __attribute__((unused)))
{

                 const int N = 1;
                 return
                     _mrcal_unproject_internal((mrcal_point3_t*)data_slice__output,
                                               (const mrcal_point2_t*)data_slice__points,
                                               N,
                                               &cookie->lensmodel,
                                               // core, distortions concatenated
                                               (const double*)data_slice__intrinsics,
                                               &cookie->precomputed);

}
#undef item__output
#undef ctype__output
#undef item__points
#undef ctype__points
#undef item__intrinsics
#undef ctype__intrinsics
#define ARGUMENTS(_) \
  _(points) \
  _(intrinsics)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___unproject__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___unproject__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___unproject(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___unproject__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___unproject__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const char* lensmodel __attribute__((unused)),___unproject__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    const char* lensmodel = NULL;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "lensmodel",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "s" ":bindings_npsp._unproject",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &lensmodel,
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {2};
    const npy_intp PROTOTYPE_intrinsics[1] = {-1};
    const npy_intp PROTOTYPE_output[1] = {3};
    int Ndims_named = 1;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___unproject__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___unproject__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___unproject__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          lensmodel,cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  lensmodel,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___unproject__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  lensmodel,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___unproject__slice failed!");
                goto done;
            }

        } while(__pywrap___unproject__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__intrinsics
#undef CHECK_CONTIGUOUS__intrinsics
#undef CHECK_CONTIGUOUS_AND_SETERROR__intrinsics

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _project_pinhole

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___project_pinhole__cookie_t;

static
bool ___project_pinhole__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___project_pinhole__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___project_pinhole__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___project_pinhole__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_project_pinhole((mrcal_point2_t*)data_slice__output,
                                        NULL,
                                        (const mrcal_point3_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output
#undef ctype__output
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___project_pinhole__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___project_pinhole__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___project_pinhole(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___project_pinhole__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___project_pinhole__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___project_pinhole__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._project_pinhole",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {3};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output[1] = {2};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___project_pinhole__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___project_pinhole__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___project_pinhole__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___project_pinhole__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___project_pinhole__slice failed!");
                goto done;
            }

        } while(__pywrap___project_pinhole__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _project_pinhole_withgrad

#define _CHECK_CONTIGUOUS__output0(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output0; i++)                               \
      if(dims_full__output0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output0; i--)                       \
      {                                                                 \
          if(strides_slice__output0[i+Ndims_slice__output0] != sizeof_element__output0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output0[i+Ndims_slice__output0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output0()              _CHECK_CONTIGUOUS__output0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output0() _CHECK_CONTIGUOUS__output0(true)


#define _CHECK_CONTIGUOUS__output1(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output1; i++)                               \
      if(dims_full__output1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output1; i--)                       \
      {                                                                 \
          if(strides_slice__output1[i+Ndims_slice__output1] != sizeof_element__output1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output1[i+Ndims_slice__output1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output1()              _CHECK_CONTIGUOUS__output1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output1() _CHECK_CONTIGUOUS__output1(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___project_pinhole_withgrad__cookie_t;

static
bool ___project_pinhole_withgrad__validate(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___project_pinhole_withgrad__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output0 npy_float64
#define item__output0(__ivar0) (*(ctype__output0*)(data_slice__output0 + (__ivar0)*strides_slice__output0[0]))
#define ctype__output1 npy_float64
#define item__output1(__ivar0,__ivar1) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___project_pinhole_withgrad__0__slice(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data_slice__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data_slice__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___project_pinhole_withgrad__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_project_pinhole((mrcal_point2_t*)data_slice__output0,
                                        (mrcal_point3_t*)data_slice__output1,
                                        (const mrcal_point3_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output0) \
  _(output1)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___project_pinhole_withgrad__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___project_pinhole_withgrad__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___project_pinhole_withgrad(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___project_pinhole_withgrad__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___project_pinhole_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___project_pinhole_withgrad__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._project_pinhole_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {3};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output0[1] = {2};
    const npy_intp PROTOTYPE_output1[2] = {2,3};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        __py__output__arg = PyTuple_New(2);
        if(__py__output__arg == NULL)
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Could not allocate output tuple of length %d", 2);
            goto done;
        }

        // I made a tuple, but I don't yet have arrays to populate it with. I'll make
        // those later, and I'll fill the tuple later
        populate_output_tuple__i = 0;
    }
    else
    {
        Py_INCREF(__py__output__arg);

        if( !PySequence_Check(__py__output__arg) )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a non-sequence was given",
                         2);
            goto done;
        }
        if( PySequence_Size(__py__output__arg) != 2 )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a sequence of length %d was given",
                         2, PySequence_Size(__py__output__arg));
            goto done;
        }

#define PULL_OUT_OUTPUT_ARRAYS(name)                                                                         \
        __py__ ## name = (PyArrayObject*)PySequence_GetItem(__py__output__arg, i++);                         \
        if(__py__ ## name == NULL || !PyArray_Check(__py__ ## name))                                         \
        {                                                                                                    \
            PyErr_SetString(PyExc_RuntimeError,                                                              \
                            "Have multiple outputs. The given 'out' array MUST contain pre-allocated arrays, but " #name " is not an array"); \
            goto done;                                                                                       \
        }
        int i=0;
        OUTPUTS(PULL_OUT_OUTPUT_ARRAYS)
#undef PULL_OUT_OUTPUT_ARRAYS
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3, i)                   \
        else if( __pywrap___project_pinhole_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___project_pinhole_withgrad__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___project_pinhole_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___project_pinhole_withgrad__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___project_pinhole_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___project_pinhole_withgrad__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS_AND_SETERROR__output0
#undef _CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS_AND_SETERROR__output1
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _unproject_pinhole

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___unproject_pinhole__cookie_t;

static
bool ___unproject_pinhole__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___unproject_pinhole__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___unproject_pinhole__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___unproject_pinhole__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_unproject_pinhole((mrcal_point3_t*)data_slice__output,
                                        NULL,
                                        (const mrcal_point2_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output
#undef ctype__output
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___unproject_pinhole__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___unproject_pinhole__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___unproject_pinhole(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___unproject_pinhole__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___unproject_pinhole__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___unproject_pinhole__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._unproject_pinhole",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {2};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output[1] = {3};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___unproject_pinhole__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___unproject_pinhole__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___unproject_pinhole__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___unproject_pinhole__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___unproject_pinhole__slice failed!");
                goto done;
            }

        } while(__pywrap___unproject_pinhole__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _unproject_pinhole_withgrad

#define _CHECK_CONTIGUOUS__output0(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output0; i++)                               \
      if(dims_full__output0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output0; i--)                       \
      {                                                                 \
          if(strides_slice__output0[i+Ndims_slice__output0] != sizeof_element__output0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output0[i+Ndims_slice__output0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output0()              _CHECK_CONTIGUOUS__output0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output0() _CHECK_CONTIGUOUS__output0(true)


#define _CHECK_CONTIGUOUS__output1(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output1; i++)                               \
      if(dims_full__output1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output1; i--)                       \
      {                                                                 \
          if(strides_slice__output1[i+Ndims_slice__output1] != sizeof_element__output1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output1[i+Ndims_slice__output1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output1()              _CHECK_CONTIGUOUS__output1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output1() _CHECK_CONTIGUOUS__output1(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___unproject_pinhole_withgrad__cookie_t;

static
bool ___unproject_pinhole_withgrad__validate(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___unproject_pinhole_withgrad__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output0 npy_float64
#define item__output0(__ivar0) (*(ctype__output0*)(data_slice__output0 + (__ivar0)*strides_slice__output0[0]))
#define ctype__output1 npy_float64
#define item__output1(__ivar0,__ivar1) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___unproject_pinhole_withgrad__0__slice(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data_slice__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data_slice__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___unproject_pinhole_withgrad__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_unproject_pinhole((mrcal_point3_t*)data_slice__output0,
                                        (mrcal_point2_t*)data_slice__output1,
                                        (const mrcal_point2_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output0) \
  _(output1)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___unproject_pinhole_withgrad__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___unproject_pinhole_withgrad__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___unproject_pinhole_withgrad(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___unproject_pinhole_withgrad__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___unproject_pinhole_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___unproject_pinhole_withgrad__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._unproject_pinhole_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {2};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output0[1] = {3};
    const npy_intp PROTOTYPE_output1[2] = {3,2};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        __py__output__arg = PyTuple_New(2);
        if(__py__output__arg == NULL)
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Could not allocate output tuple of length %d", 2);
            goto done;
        }

        // I made a tuple, but I don't yet have arrays to populate it with. I'll make
        // those later, and I'll fill the tuple later
        populate_output_tuple__i = 0;
    }
    else
    {
        Py_INCREF(__py__output__arg);

        if( !PySequence_Check(__py__output__arg) )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a non-sequence was given",
                         2);
            goto done;
        }
        if( PySequence_Size(__py__output__arg) != 2 )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a sequence of length %d was given",
                         2, PySequence_Size(__py__output__arg));
            goto done;
        }

#define PULL_OUT_OUTPUT_ARRAYS(name)                                                                         \
        __py__ ## name = (PyArrayObject*)PySequence_GetItem(__py__output__arg, i++);                         \
        if(__py__ ## name == NULL || !PyArray_Check(__py__ ## name))                                         \
        {                                                                                                    \
            PyErr_SetString(PyExc_RuntimeError,                                                              \
                            "Have multiple outputs. The given 'out' array MUST contain pre-allocated arrays, but " #name " is not an array"); \
            goto done;                                                                                       \
        }
        int i=0;
        OUTPUTS(PULL_OUT_OUTPUT_ARRAYS)
#undef PULL_OUT_OUTPUT_ARRAYS
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3, i)                   \
        else if( __pywrap___unproject_pinhole_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___unproject_pinhole_withgrad__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___unproject_pinhole_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___unproject_pinhole_withgrad__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___unproject_pinhole_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___unproject_pinhole_withgrad__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS_AND_SETERROR__output0
#undef _CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS_AND_SETERROR__output1
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _project_stereographic

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___project_stereographic__cookie_t;

static
bool ___project_stereographic__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___project_stereographic__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___project_stereographic__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___project_stereographic__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_project_stereographic((mrcal_point2_t*)data_slice__output,
                                        NULL,
                                        (const mrcal_point3_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output
#undef ctype__output
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___project_stereographic__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___project_stereographic__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___project_stereographic(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___project_stereographic__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___project_stereographic__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___project_stereographic__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._project_stereographic",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {3};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output[1] = {2};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___project_stereographic__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___project_stereographic__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___project_stereographic__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___project_stereographic__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___project_stereographic__slice failed!");
                goto done;
            }

        } while(__pywrap___project_stereographic__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _project_stereographic_withgrad

#define _CHECK_CONTIGUOUS__output0(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output0; i++)                               \
      if(dims_full__output0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output0; i--)                       \
      {                                                                 \
          if(strides_slice__output0[i+Ndims_slice__output0] != sizeof_element__output0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output0[i+Ndims_slice__output0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output0()              _CHECK_CONTIGUOUS__output0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output0() _CHECK_CONTIGUOUS__output0(true)


#define _CHECK_CONTIGUOUS__output1(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output1; i++)                               \
      if(dims_full__output1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output1; i--)                       \
      {                                                                 \
          if(strides_slice__output1[i+Ndims_slice__output1] != sizeof_element__output1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output1[i+Ndims_slice__output1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output1()              _CHECK_CONTIGUOUS__output1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output1() _CHECK_CONTIGUOUS__output1(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___project_stereographic_withgrad__cookie_t;

static
bool ___project_stereographic_withgrad__validate(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___project_stereographic_withgrad__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output0 npy_float64
#define item__output0(__ivar0) (*(ctype__output0*)(data_slice__output0 + (__ivar0)*strides_slice__output0[0]))
#define ctype__output1 npy_float64
#define item__output1(__ivar0,__ivar1) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___project_stereographic_withgrad__0__slice(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data_slice__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data_slice__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___project_stereographic_withgrad__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_project_stereographic((mrcal_point2_t*)data_slice__output0,
                                        (mrcal_point3_t*)data_slice__output1,
                                        (const mrcal_point3_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output0) \
  _(output1)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___project_stereographic_withgrad__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___project_stereographic_withgrad__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___project_stereographic_withgrad(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___project_stereographic_withgrad__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___project_stereographic_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___project_stereographic_withgrad__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._project_stereographic_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {3};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output0[1] = {2};
    const npy_intp PROTOTYPE_output1[2] = {2,3};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        __py__output__arg = PyTuple_New(2);
        if(__py__output__arg == NULL)
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Could not allocate output tuple of length %d", 2);
            goto done;
        }

        // I made a tuple, but I don't yet have arrays to populate it with. I'll make
        // those later, and I'll fill the tuple later
        populate_output_tuple__i = 0;
    }
    else
    {
        Py_INCREF(__py__output__arg);

        if( !PySequence_Check(__py__output__arg) )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a non-sequence was given",
                         2);
            goto done;
        }
        if( PySequence_Size(__py__output__arg) != 2 )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a sequence of length %d was given",
                         2, PySequence_Size(__py__output__arg));
            goto done;
        }

#define PULL_OUT_OUTPUT_ARRAYS(name)                                                                         \
        __py__ ## name = (PyArrayObject*)PySequence_GetItem(__py__output__arg, i++);                         \
        if(__py__ ## name == NULL || !PyArray_Check(__py__ ## name))                                         \
        {                                                                                                    \
            PyErr_SetString(PyExc_RuntimeError,                                                              \
                            "Have multiple outputs. The given 'out' array MUST contain pre-allocated arrays, but " #name " is not an array"); \
            goto done;                                                                                       \
        }
        int i=0;
        OUTPUTS(PULL_OUT_OUTPUT_ARRAYS)
#undef PULL_OUT_OUTPUT_ARRAYS
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3, i)                   \
        else if( __pywrap___project_stereographic_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___project_stereographic_withgrad__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___project_stereographic_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___project_stereographic_withgrad__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___project_stereographic_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___project_stereographic_withgrad__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS_AND_SETERROR__output0
#undef _CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS_AND_SETERROR__output1
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _unproject_stereographic

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___unproject_stereographic__cookie_t;

static
bool ___unproject_stereographic__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___unproject_stereographic__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___unproject_stereographic__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___unproject_stereographic__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_unproject_stereographic((mrcal_point3_t*)data_slice__output,
                                        NULL,
                                        (const mrcal_point2_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output
#undef ctype__output
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___unproject_stereographic__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___unproject_stereographic__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___unproject_stereographic(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___unproject_stereographic__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___unproject_stereographic__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___unproject_stereographic__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._unproject_stereographic",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {2};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output[1] = {3};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___unproject_stereographic__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___unproject_stereographic__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___unproject_stereographic__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___unproject_stereographic__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___unproject_stereographic__slice failed!");
                goto done;
            }

        } while(__pywrap___unproject_stereographic__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _unproject_stereographic_withgrad

#define _CHECK_CONTIGUOUS__output0(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output0; i++)                               \
      if(dims_full__output0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output0; i--)                       \
      {                                                                 \
          if(strides_slice__output0[i+Ndims_slice__output0] != sizeof_element__output0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output0[i+Ndims_slice__output0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output0()              _CHECK_CONTIGUOUS__output0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output0() _CHECK_CONTIGUOUS__output0(true)


#define _CHECK_CONTIGUOUS__output1(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output1; i++)                               \
      if(dims_full__output1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output1; i--)                       \
      {                                                                 \
          if(strides_slice__output1[i+Ndims_slice__output1] != sizeof_element__output1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output1[i+Ndims_slice__output1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output1()              _CHECK_CONTIGUOUS__output1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output1() _CHECK_CONTIGUOUS__output1(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___unproject_stereographic_withgrad__cookie_t;

static
bool ___unproject_stereographic_withgrad__validate(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___unproject_stereographic_withgrad__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output0 npy_float64
#define item__output0(__ivar0) (*(ctype__output0*)(data_slice__output0 + (__ivar0)*strides_slice__output0[0]))
#define ctype__output1 npy_float64
#define item__output1(__ivar0,__ivar1) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___unproject_stereographic_withgrad__0__slice(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data_slice__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data_slice__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___unproject_stereographic_withgrad__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_unproject_stereographic((mrcal_point3_t*)data_slice__output0,
                                        (mrcal_point2_t*)data_slice__output1,
                                        (const mrcal_point2_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output0) \
  _(output1)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___unproject_stereographic_withgrad__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___unproject_stereographic_withgrad__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___unproject_stereographic_withgrad(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___unproject_stereographic_withgrad__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___unproject_stereographic_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___unproject_stereographic_withgrad__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._unproject_stereographic_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {2};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output0[1] = {3};
    const npy_intp PROTOTYPE_output1[2] = {3,2};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        __py__output__arg = PyTuple_New(2);
        if(__py__output__arg == NULL)
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Could not allocate output tuple of length %d", 2);
            goto done;
        }

        // I made a tuple, but I don't yet have arrays to populate it with. I'll make
        // those later, and I'll fill the tuple later
        populate_output_tuple__i = 0;
    }
    else
    {
        Py_INCREF(__py__output__arg);

        if( !PySequence_Check(__py__output__arg) )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a non-sequence was given",
                         2);
            goto done;
        }
        if( PySequence_Size(__py__output__arg) != 2 )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a sequence of length %d was given",
                         2, PySequence_Size(__py__output__arg));
            goto done;
        }

#define PULL_OUT_OUTPUT_ARRAYS(name)                                                                         \
        __py__ ## name = (PyArrayObject*)PySequence_GetItem(__py__output__arg, i++);                         \
        if(__py__ ## name == NULL || !PyArray_Check(__py__ ## name))                                         \
        {                                                                                                    \
            PyErr_SetString(PyExc_RuntimeError,                                                              \
                            "Have multiple outputs. The given 'out' array MUST contain pre-allocated arrays, but " #name " is not an array"); \
            goto done;                                                                                       \
        }
        int i=0;
        OUTPUTS(PULL_OUT_OUTPUT_ARRAYS)
#undef PULL_OUT_OUTPUT_ARRAYS
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3, i)                   \
        else if( __pywrap___unproject_stereographic_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___unproject_stereographic_withgrad__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___unproject_stereographic_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___unproject_stereographic_withgrad__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___unproject_stereographic_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___unproject_stereographic_withgrad__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS_AND_SETERROR__output0
#undef _CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS_AND_SETERROR__output1
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _project_lonlat

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___project_lonlat__cookie_t;

static
bool ___project_lonlat__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___project_lonlat__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___project_lonlat__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___project_lonlat__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_project_lonlat((mrcal_point2_t*)data_slice__output,
                                        NULL,
                                        (const mrcal_point3_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output
#undef ctype__output
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___project_lonlat__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___project_lonlat__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___project_lonlat(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___project_lonlat__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___project_lonlat__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___project_lonlat__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._project_lonlat",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {3};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output[1] = {2};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___project_lonlat__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___project_lonlat__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___project_lonlat__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___project_lonlat__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___project_lonlat__slice failed!");
                goto done;
            }

        } while(__pywrap___project_lonlat__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _project_lonlat_withgrad

#define _CHECK_CONTIGUOUS__output0(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output0; i++)                               \
      if(dims_full__output0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output0; i--)                       \
      {                                                                 \
          if(strides_slice__output0[i+Ndims_slice__output0] != sizeof_element__output0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output0[i+Ndims_slice__output0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output0()              _CHECK_CONTIGUOUS__output0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output0() _CHECK_CONTIGUOUS__output0(true)


#define _CHECK_CONTIGUOUS__output1(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output1; i++)                               \
      if(dims_full__output1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output1; i--)                       \
      {                                                                 \
          if(strides_slice__output1[i+Ndims_slice__output1] != sizeof_element__output1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output1[i+Ndims_slice__output1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output1()              _CHECK_CONTIGUOUS__output1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output1() _CHECK_CONTIGUOUS__output1(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___project_lonlat_withgrad__cookie_t;

static
bool ___project_lonlat_withgrad__validate(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___project_lonlat_withgrad__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output0 npy_float64
#define item__output0(__ivar0) (*(ctype__output0*)(data_slice__output0 + (__ivar0)*strides_slice__output0[0]))
#define ctype__output1 npy_float64
#define item__output1(__ivar0,__ivar1) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___project_lonlat_withgrad__0__slice(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data_slice__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data_slice__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___project_lonlat_withgrad__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_project_lonlat((mrcal_point2_t*)data_slice__output0,
                                        (mrcal_point3_t*)data_slice__output1,
                                        (const mrcal_point3_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output0) \
  _(output1)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___project_lonlat_withgrad__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___project_lonlat_withgrad__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___project_lonlat_withgrad(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___project_lonlat_withgrad__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___project_lonlat_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___project_lonlat_withgrad__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._project_lonlat_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {3};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output0[1] = {2};
    const npy_intp PROTOTYPE_output1[2] = {2,3};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        __py__output__arg = PyTuple_New(2);
        if(__py__output__arg == NULL)
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Could not allocate output tuple of length %d", 2);
            goto done;
        }

        // I made a tuple, but I don't yet have arrays to populate it with. I'll make
        // those later, and I'll fill the tuple later
        populate_output_tuple__i = 0;
    }
    else
    {
        Py_INCREF(__py__output__arg);

        if( !PySequence_Check(__py__output__arg) )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a non-sequence was given",
                         2);
            goto done;
        }
        if( PySequence_Size(__py__output__arg) != 2 )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a sequence of length %d was given",
                         2, PySequence_Size(__py__output__arg));
            goto done;
        }

#define PULL_OUT_OUTPUT_ARRAYS(name)                                                                         \
        __py__ ## name = (PyArrayObject*)PySequence_GetItem(__py__output__arg, i++);                         \
        if(__py__ ## name == NULL || !PyArray_Check(__py__ ## name))                                         \
        {                                                                                                    \
            PyErr_SetString(PyExc_RuntimeError,                                                              \
                            "Have multiple outputs. The given 'out' array MUST contain pre-allocated arrays, but " #name " is not an array"); \
            goto done;                                                                                       \
        }
        int i=0;
        OUTPUTS(PULL_OUT_OUTPUT_ARRAYS)
#undef PULL_OUT_OUTPUT_ARRAYS
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3, i)                   \
        else if( __pywrap___project_lonlat_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___project_lonlat_withgrad__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___project_lonlat_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___project_lonlat_withgrad__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___project_lonlat_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___project_lonlat_withgrad__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS_AND_SETERROR__output0
#undef _CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS_AND_SETERROR__output1
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _unproject_lonlat

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___unproject_lonlat__cookie_t;

static
bool ___unproject_lonlat__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___unproject_lonlat__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___unproject_lonlat__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___unproject_lonlat__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_unproject_lonlat((mrcal_point3_t*)data_slice__output,
                                        NULL,
                                        (const mrcal_point2_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output
#undef ctype__output
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___unproject_lonlat__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___unproject_lonlat__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___unproject_lonlat(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___unproject_lonlat__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___unproject_lonlat__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___unproject_lonlat__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._unproject_lonlat",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {2};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output[1] = {3};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___unproject_lonlat__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___unproject_lonlat__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___unproject_lonlat__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___unproject_lonlat__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___unproject_lonlat__slice failed!");
                goto done;
            }

        } while(__pywrap___unproject_lonlat__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _unproject_lonlat_withgrad

#define _CHECK_CONTIGUOUS__output0(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output0; i++)                               \
      if(dims_full__output0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output0; i--)                       \
      {                                                                 \
          if(strides_slice__output0[i+Ndims_slice__output0] != sizeof_element__output0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output0[i+Ndims_slice__output0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output0()              _CHECK_CONTIGUOUS__output0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output0() _CHECK_CONTIGUOUS__output0(true)


#define _CHECK_CONTIGUOUS__output1(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output1; i++)                               \
      if(dims_full__output1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output1; i--)                       \
      {                                                                 \
          if(strides_slice__output1[i+Ndims_slice__output1] != sizeof_element__output1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output1[i+Ndims_slice__output1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output1()              _CHECK_CONTIGUOUS__output1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output1() _CHECK_CONTIGUOUS__output1(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___unproject_lonlat_withgrad__cookie_t;

static
bool ___unproject_lonlat_withgrad__validate(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___unproject_lonlat_withgrad__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output0 npy_float64
#define item__output0(__ivar0) (*(ctype__output0*)(data_slice__output0 + (__ivar0)*strides_slice__output0[0]))
#define ctype__output1 npy_float64
#define item__output1(__ivar0,__ivar1) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___unproject_lonlat_withgrad__0__slice(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data_slice__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data_slice__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___unproject_lonlat_withgrad__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_unproject_lonlat((mrcal_point3_t*)data_slice__output0,
                                        (mrcal_point2_t*)data_slice__output1,
                                        (const mrcal_point2_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output0) \
  _(output1)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___unproject_lonlat_withgrad__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___unproject_lonlat_withgrad__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___unproject_lonlat_withgrad(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___unproject_lonlat_withgrad__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___unproject_lonlat_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___unproject_lonlat_withgrad__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._unproject_lonlat_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {2};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output0[1] = {3};
    const npy_intp PROTOTYPE_output1[2] = {3,2};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        __py__output__arg = PyTuple_New(2);
        if(__py__output__arg == NULL)
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Could not allocate output tuple of length %d", 2);
            goto done;
        }

        // I made a tuple, but I don't yet have arrays to populate it with. I'll make
        // those later, and I'll fill the tuple later
        populate_output_tuple__i = 0;
    }
    else
    {
        Py_INCREF(__py__output__arg);

        if( !PySequence_Check(__py__output__arg) )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a non-sequence was given",
                         2);
            goto done;
        }
        if( PySequence_Size(__py__output__arg) != 2 )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a sequence of length %d was given",
                         2, PySequence_Size(__py__output__arg));
            goto done;
        }

#define PULL_OUT_OUTPUT_ARRAYS(name)                                                                         \
        __py__ ## name = (PyArrayObject*)PySequence_GetItem(__py__output__arg, i++);                         \
        if(__py__ ## name == NULL || !PyArray_Check(__py__ ## name))                                         \
        {                                                                                                    \
            PyErr_SetString(PyExc_RuntimeError,                                                              \
                            "Have multiple outputs. The given 'out' array MUST contain pre-allocated arrays, but " #name " is not an array"); \
            goto done;                                                                                       \
        }
        int i=0;
        OUTPUTS(PULL_OUT_OUTPUT_ARRAYS)
#undef PULL_OUT_OUTPUT_ARRAYS
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3, i)                   \
        else if( __pywrap___unproject_lonlat_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___unproject_lonlat_withgrad__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___unproject_lonlat_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___unproject_lonlat_withgrad__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___unproject_lonlat_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___unproject_lonlat_withgrad__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS_AND_SETERROR__output0
#undef _CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS_AND_SETERROR__output1
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _project_latlon

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___project_latlon__cookie_t;

static
bool ___project_latlon__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___project_latlon__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___project_latlon__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___project_latlon__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_project_latlon((mrcal_point2_t*)data_slice__output,
                                        NULL,
                                        (const mrcal_point3_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output
#undef ctype__output
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___project_latlon__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___project_latlon__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___project_latlon(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___project_latlon__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___project_latlon__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___project_latlon__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._project_latlon",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {3};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output[1] = {2};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___project_latlon__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___project_latlon__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___project_latlon__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___project_latlon__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___project_latlon__slice failed!");
                goto done;
            }

        } while(__pywrap___project_latlon__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _project_latlon_withgrad

#define _CHECK_CONTIGUOUS__output0(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output0; i++)                               \
      if(dims_full__output0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output0; i--)                       \
      {                                                                 \
          if(strides_slice__output0[i+Ndims_slice__output0] != sizeof_element__output0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output0[i+Ndims_slice__output0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output0()              _CHECK_CONTIGUOUS__output0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output0() _CHECK_CONTIGUOUS__output0(true)


#define _CHECK_CONTIGUOUS__output1(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output1; i++)                               \
      if(dims_full__output1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output1; i--)                       \
      {                                                                 \
          if(strides_slice__output1[i+Ndims_slice__output1] != sizeof_element__output1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output1[i+Ndims_slice__output1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output1()              _CHECK_CONTIGUOUS__output1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output1() _CHECK_CONTIGUOUS__output1(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___project_latlon_withgrad__cookie_t;

static
bool ___project_latlon_withgrad__validate(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___project_latlon_withgrad__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output0 npy_float64
#define item__output0(__ivar0) (*(ctype__output0*)(data_slice__output0 + (__ivar0)*strides_slice__output0[0]))
#define ctype__output1 npy_float64
#define item__output1(__ivar0,__ivar1) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___project_latlon_withgrad__0__slice(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data_slice__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data_slice__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___project_latlon_withgrad__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_project_latlon((mrcal_point2_t*)data_slice__output0,
                                        (mrcal_point3_t*)data_slice__output1,
                                        (const mrcal_point3_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output0) \
  _(output1)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___project_latlon_withgrad__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___project_latlon_withgrad__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___project_latlon_withgrad(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___project_latlon_withgrad__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___project_latlon_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___project_latlon_withgrad__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._project_latlon_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {3};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output0[1] = {2};
    const npy_intp PROTOTYPE_output1[2] = {2,3};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        __py__output__arg = PyTuple_New(2);
        if(__py__output__arg == NULL)
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Could not allocate output tuple of length %d", 2);
            goto done;
        }

        // I made a tuple, but I don't yet have arrays to populate it with. I'll make
        // those later, and I'll fill the tuple later
        populate_output_tuple__i = 0;
    }
    else
    {
        Py_INCREF(__py__output__arg);

        if( !PySequence_Check(__py__output__arg) )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a non-sequence was given",
                         2);
            goto done;
        }
        if( PySequence_Size(__py__output__arg) != 2 )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a sequence of length %d was given",
                         2, PySequence_Size(__py__output__arg));
            goto done;
        }

#define PULL_OUT_OUTPUT_ARRAYS(name)                                                                         \
        __py__ ## name = (PyArrayObject*)PySequence_GetItem(__py__output__arg, i++);                         \
        if(__py__ ## name == NULL || !PyArray_Check(__py__ ## name))                                         \
        {                                                                                                    \
            PyErr_SetString(PyExc_RuntimeError,                                                              \
                            "Have multiple outputs. The given 'out' array MUST contain pre-allocated arrays, but " #name " is not an array"); \
            goto done;                                                                                       \
        }
        int i=0;
        OUTPUTS(PULL_OUT_OUTPUT_ARRAYS)
#undef PULL_OUT_OUTPUT_ARRAYS
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3, i)                   \
        else if( __pywrap___project_latlon_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___project_latlon_withgrad__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___project_latlon_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___project_latlon_withgrad__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___project_latlon_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___project_latlon_withgrad__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS_AND_SETERROR__output0
#undef _CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS_AND_SETERROR__output1
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _unproject_latlon

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___unproject_latlon__cookie_t;

static
bool ___unproject_latlon__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___unproject_latlon__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___unproject_latlon__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___unproject_latlon__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_unproject_latlon((mrcal_point3_t*)data_slice__output,
                                        NULL,
                                        (const mrcal_point2_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output
#undef ctype__output
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___unproject_latlon__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___unproject_latlon__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___unproject_latlon(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___unproject_latlon__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___unproject_latlon__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___unproject_latlon__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._unproject_latlon",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {2};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output[1] = {3};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___unproject_latlon__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___unproject_latlon__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___unproject_latlon__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___unproject_latlon__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___unproject_latlon__slice failed!");
                goto done;
            }

        } while(__pywrap___unproject_latlon__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _unproject_latlon_withgrad

#define _CHECK_CONTIGUOUS__output0(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output0; i++)                               \
      if(dims_full__output0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output0; i--)                       \
      {                                                                 \
          if(strides_slice__output0[i+Ndims_slice__output0] != sizeof_element__output0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output0[i+Ndims_slice__output0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output0()              _CHECK_CONTIGUOUS__output0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output0() _CHECK_CONTIGUOUS__output0(true)


#define _CHECK_CONTIGUOUS__output1(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output1; i++)                               \
      if(dims_full__output1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output1; i--)                       \
      {                                                                 \
          if(strides_slice__output1[i+Ndims_slice__output1] != sizeof_element__output1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output1[i+Ndims_slice__output1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output1()              _CHECK_CONTIGUOUS__output1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output1() _CHECK_CONTIGUOUS__output1(true)


#define _CHECK_CONTIGUOUS__points(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__points; i++)                               \
      if(dims_full__points[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__points; i--)                       \
      {                                                                 \
          if(strides_slice__points[i+Ndims_slice__points] != sizeof_element__points*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'points' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__points[i+Ndims_slice__points];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__points()              _CHECK_CONTIGUOUS__points(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__points() _CHECK_CONTIGUOUS__points(true)


#define _CHECK_CONTIGUOUS__fxycxy(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy; i++)                               \
      if(dims_full__fxycxy[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy[i+Ndims_slice__fxycxy] != sizeof_element__fxycxy*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy[i+Ndims_slice__fxycxy];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy()              _CHECK_CONTIGUOUS__fxycxy(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy() _CHECK_CONTIGUOUS__fxycxy(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__points() && CHECK_CONTIGUOUS__fxycxy()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__points() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy()

typedef struct {  } ___unproject_latlon_withgrad__cookie_t;

static
bool ___unproject_latlon_withgrad__validate(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data__fxycxy __attribute__((unused)),
  ___unproject_latlon_withgrad__cookie_t* cookie __attribute__((unused)))
{
return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output0 npy_float64
#define item__output0(__ivar0) (*(ctype__output0*)(data_slice__output0 + (__ivar0)*strides_slice__output0[0]))
#define ctype__output1 npy_float64
#define item__output1(__ivar0,__ivar1) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]))
#define ctype__points npy_float64
#define item__points(__ivar0) (*(ctype__points*)(data_slice__points + (__ivar0)*strides_slice__points[0]))
#define ctype__fxycxy npy_float64
#define item__fxycxy(__ivar0) (*(ctype__fxycxy*)(data_slice__fxycxy + (__ivar0)*strides_slice__fxycxy[0]))

static
bool ___unproject_latlon_withgrad__0__slice(
  const int Ndims_full__output0 __attribute__((unused)),
  const npy_intp* dims_full__output0 __attribute__((unused)),
  const npy_intp* strides_full__output0 __attribute__((unused)),
  const int Ndims_slice__output0 __attribute__((unused)),
  const npy_intp* dims_slice__output0 __attribute__((unused)),
  const npy_intp* strides_slice__output0 __attribute__((unused)),
  npy_intp sizeof_element__output0 __attribute__((unused)),
  void* data_slice__output0 __attribute__((unused)),
  const int Ndims_full__output1 __attribute__((unused)),
  const npy_intp* dims_full__output1 __attribute__((unused)),
  const npy_intp* strides_full__output1 __attribute__((unused)),
  const int Ndims_slice__output1 __attribute__((unused)),
  const npy_intp* dims_slice__output1 __attribute__((unused)),
  const npy_intp* strides_slice__output1 __attribute__((unused)),
  npy_intp sizeof_element__output1 __attribute__((unused)),
  void* data_slice__output1 __attribute__((unused)),
  const int Ndims_full__points __attribute__((unused)),
  const npy_intp* dims_full__points __attribute__((unused)),
  const npy_intp* strides_full__points __attribute__((unused)),
  const int Ndims_slice__points __attribute__((unused)),
  const npy_intp* dims_slice__points __attribute__((unused)),
  const npy_intp* strides_slice__points __attribute__((unused)),
  npy_intp sizeof_element__points __attribute__((unused)),
  void* data_slice__points __attribute__((unused)),
  const int Ndims_full__fxycxy __attribute__((unused)),
  const npy_intp* dims_full__fxycxy __attribute__((unused)),
  const npy_intp* strides_full__fxycxy __attribute__((unused)),
  const int Ndims_slice__fxycxy __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy __attribute__((unused)),
  npy_intp sizeof_element__fxycxy __attribute__((unused)),
  void* data_slice__fxycxy __attribute__((unused)),
  ___unproject_latlon_withgrad__cookie_t* cookie __attribute__((unused)))
{

            const double* fxycxy = (const double*)data_slice__fxycxy;
            mrcal_unproject_latlon((mrcal_point3_t*)data_slice__output0,
                                        (mrcal_point2_t*)data_slice__output1,
                                        (const mrcal_point2_t*)data_slice__points,
                                        1,
                                        fxycxy);
            return true;
}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__points
#undef ctype__points
#undef item__fxycxy
#undef ctype__fxycxy
#define ARGUMENTS(_) \
  _(points) \
  _(fxycxy)

#define OUTPUTS(_) \
  _(output0) \
  _(output1)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___unproject_latlon_withgrad__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___unproject_latlon_withgrad__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___unproject_latlon_withgrad(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___unproject_latlon_withgrad__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___unproject_latlon_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___unproject_latlon_withgrad__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._unproject_latlon_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_points[1] = {2};
    const npy_intp PROTOTYPE_fxycxy[1] = {4};
    const npy_intp PROTOTYPE_output0[1] = {3};
    const npy_intp PROTOTYPE_output1[2] = {3,2};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        __py__output__arg = PyTuple_New(2);
        if(__py__output__arg == NULL)
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Could not allocate output tuple of length %d", 2);
            goto done;
        }

        // I made a tuple, but I don't yet have arrays to populate it with. I'll make
        // those later, and I'll fill the tuple later
        populate_output_tuple__i = 0;
    }
    else
    {
        Py_INCREF(__py__output__arg);

        if( !PySequence_Check(__py__output__arg) )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a non-sequence was given",
                         2);
            goto done;
        }
        if( PySequence_Size(__py__output__arg) != 2 )
        {
            PyErr_Format(PyExc_RuntimeError,
                         "Have multiple outputs. The given 'out' argument is expected to be a sequence of length %d, but a sequence of length %d was given",
                         2, PySequence_Size(__py__output__arg));
            goto done;
        }

#define PULL_OUT_OUTPUT_ARRAYS(name)                                                                         \
        __py__ ## name = (PyArrayObject*)PySequence_GetItem(__py__output__arg, i++);                         \
        if(__py__ ## name == NULL || !PyArray_Check(__py__ ## name))                                         \
        {                                                                                                    \
            PyErr_SetString(PyExc_RuntimeError,                                                              \
                            "Have multiple outputs. The given 'out' array MUST contain pre-allocated arrays, but " #name " is not an array"); \
            goto done;                                                                                       \
        }
        int i=0;
        OUTPUTS(PULL_OUT_OUTPUT_ARRAYS)
#undef PULL_OUT_OUTPUT_ARRAYS
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3, i)                   \
        else if( __pywrap___unproject_latlon_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___unproject_latlon_withgrad__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64,float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___unproject_latlon_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___unproject_latlon_withgrad__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___unproject_latlon_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___unproject_latlon_withgrad__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS__output0
#undef CHECK_CONTIGUOUS_AND_SETERROR__output0
#undef _CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS__output1
#undef CHECK_CONTIGUOUS_AND_SETERROR__output1
#undef _CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS__points
#undef CHECK_CONTIGUOUS_AND_SETERROR__points
#undef _CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS__fxycxy
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _A_Jt_J_At

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__A(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__A; i++)                               \
      if(dims_full__A[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__A; i--)                       \
      {                                                                 \
          if(strides_slice__A[i+Ndims_slice__A] != sizeof_element__A*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'A' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__A[i+Ndims_slice__A];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__A()              _CHECK_CONTIGUOUS__A(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__A() _CHECK_CONTIGUOUS__A(true)


#define _CHECK_CONTIGUOUS__Jp(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__Jp; i++)                               \
      if(dims_full__Jp[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Jp; i--)                       \
      {                                                                 \
          if(strides_slice__Jp[i+Ndims_slice__Jp] != sizeof_element__Jp*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Jp' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Jp[i+Ndims_slice__Jp];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Jp()              _CHECK_CONTIGUOUS__Jp(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Jp() _CHECK_CONTIGUOUS__Jp(true)


#define _CHECK_CONTIGUOUS__Ji(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__Ji; i++)                               \
      if(dims_full__Ji[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Ji; i--)                       \
      {                                                                 \
          if(strides_slice__Ji[i+Ndims_slice__Ji] != sizeof_element__Ji*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Ji' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Ji[i+Ndims_slice__Ji];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Ji()              _CHECK_CONTIGUOUS__Ji(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Ji() _CHECK_CONTIGUOUS__Ji(true)


#define _CHECK_CONTIGUOUS__Jx(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__Jx; i++)                               \
      if(dims_full__Jx[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Jx; i--)                       \
      {                                                                 \
          if(strides_slice__Jx[i+Ndims_slice__Jx] != sizeof_element__Jx*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Jx' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Jx[i+Ndims_slice__Jx];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Jx()              _CHECK_CONTIGUOUS__Jx(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Jx() _CHECK_CONTIGUOUS__Jx(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__A() && CHECK_CONTIGUOUS__Jp() && CHECK_CONTIGUOUS__Ji() && CHECK_CONTIGUOUS__Jx()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__A() && CHECK_CONTIGUOUS_AND_SETERROR__Jp() && CHECK_CONTIGUOUS_AND_SETERROR__Ji() && CHECK_CONTIGUOUS_AND_SETERROR__Jx()

typedef struct {  } ___A_Jt_J_At__cookie_t;

static
bool ___A_Jt_J_At__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__A __attribute__((unused)),
  const npy_intp* dims_full__A __attribute__((unused)),
  const npy_intp* strides_full__A __attribute__((unused)),
  const int Ndims_slice__A __attribute__((unused)),
  const npy_intp* dims_slice__A __attribute__((unused)),
  const npy_intp* strides_slice__A __attribute__((unused)),
  npy_intp sizeof_element__A __attribute__((unused)),
  void* data__A __attribute__((unused)),
  const int Ndims_full__Jp __attribute__((unused)),
  const npy_intp* dims_full__Jp __attribute__((unused)),
  const npy_intp* strides_full__Jp __attribute__((unused)),
  const int Ndims_slice__Jp __attribute__((unused)),
  const npy_intp* dims_slice__Jp __attribute__((unused)),
  const npy_intp* strides_slice__Jp __attribute__((unused)),
  npy_intp sizeof_element__Jp __attribute__((unused)),
  void* data__Jp __attribute__((unused)),
  const int Ndims_full__Ji __attribute__((unused)),
  const npy_intp* dims_full__Ji __attribute__((unused)),
  const npy_intp* strides_full__Ji __attribute__((unused)),
  const int Ndims_slice__Ji __attribute__((unused)),
  const npy_intp* dims_slice__Ji __attribute__((unused)),
  const npy_intp* strides_slice__Ji __attribute__((unused)),
  npy_intp sizeof_element__Ji __attribute__((unused)),
  void* data__Ji __attribute__((unused)),
  const int Ndims_full__Jx __attribute__((unused)),
  const npy_intp* dims_full__Jx __attribute__((unused)),
  const npy_intp* strides_full__Jx __attribute__((unused)),
  const int Ndims_slice__Jx __attribute__((unused)),
  const npy_intp* dims_slice__Jx __attribute__((unused)),
  const npy_intp* strides_slice__Jx __attribute__((unused)),
  npy_intp sizeof_element__Jx __attribute__((unused)),
  void* data__Jx __attribute__((unused)),
  const int* Nleading_rows_J __attribute__((unused)),
  ___A_Jt_J_At__cookie_t* cookie __attribute__((unused)))
{

            if(*Nleading_rows_J <= 0)
            {
                PyErr_Format(PyExc_RuntimeError,
                             "Nleading_rows_J must be passed, and must be > 0");
                return false;
            }
            return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_float64
#define item__output(__ivar0,__ivar1) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]+ (__ivar1)*strides_slice__output[1]))
#define ctype__A npy_int32
#define item__A(__ivar0,__ivar1) (*(ctype__A*)(data_slice__A + (__ivar0)*strides_slice__A[0]+ (__ivar1)*strides_slice__A[1]))
#define ctype__Jp npy_int32
#define item__Jp(__ivar0) (*(ctype__Jp*)(data_slice__Jp + (__ivar0)*strides_slice__Jp[0]))
#define ctype__Ji npy_float64
#define item__Ji(__ivar0) (*(ctype__Ji*)(data_slice__Ji + (__ivar0)*strides_slice__Ji[0]))
#define ctype__Jx npy_float64
#define item__Jx(__ivar0) (*(ctype__Jx*)(data_slice__Jx + (__ivar0)*strides_slice__Jx[0]))

static
bool ___A_Jt_J_At__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__A __attribute__((unused)),
  const npy_intp* dims_full__A __attribute__((unused)),
  const npy_intp* strides_full__A __attribute__((unused)),
  const int Ndims_slice__A __attribute__((unused)),
  const npy_intp* dims_slice__A __attribute__((unused)),
  const npy_intp* strides_slice__A __attribute__((unused)),
  npy_intp sizeof_element__A __attribute__((unused)),
  void* data_slice__A __attribute__((unused)),
  const int Ndims_full__Jp __attribute__((unused)),
  const npy_intp* dims_full__Jp __attribute__((unused)),
  const npy_intp* strides_full__Jp __attribute__((unused)),
  const int Ndims_slice__Jp __attribute__((unused)),
  const npy_intp* dims_slice__Jp __attribute__((unused)),
  const npy_intp* strides_slice__Jp __attribute__((unused)),
  npy_intp sizeof_element__Jp __attribute__((unused)),
  void* data_slice__Jp __attribute__((unused)),
  const int Ndims_full__Ji __attribute__((unused)),
  const npy_intp* dims_full__Ji __attribute__((unused)),
  const npy_intp* strides_full__Ji __attribute__((unused)),
  const int Ndims_slice__Ji __attribute__((unused)),
  const npy_intp* dims_slice__Ji __attribute__((unused)),
  const npy_intp* strides_slice__Ji __attribute__((unused)),
  npy_intp sizeof_element__Ji __attribute__((unused)),
  void* data_slice__Ji __attribute__((unused)),
  const int Ndims_full__Jx __attribute__((unused)),
  const npy_intp* dims_full__Jx __attribute__((unused)),
  const npy_intp* strides_full__Jx __attribute__((unused)),
  const int Ndims_slice__Jx __attribute__((unused)),
  const npy_intp* dims_slice__Jx __attribute__((unused)),
  const npy_intp* strides_slice__Jx __attribute__((unused)),
  npy_intp sizeof_element__Jx __attribute__((unused)),
  void* data_slice__Jx __attribute__((unused)),
  const int* Nleading_rows_J __attribute__((unused)),
  ___A_Jt_J_At__cookie_t* cookie __attribute__((unused)))
{


                 // I'm computing A Jt J At = sum(outer(ja,ja)) where ja is each
                 // row of matmult(J,At). Rows of matmult(J,At) are
                 // matmult(jt,At) where jt are rows of J. So the logic is:
                 //
                 //   For each row jt of J:
                 //     jta = matmult(jt,At); // jta has shape (Nx,)
                 //     accumulate( outer(jta,jta) )


                 int32_t Nx     = dims_slice__A[0];
                 int32_t Nstate = dims_slice__A[1];

                 const double*   A = (const double* )data_slice__A;
                 const int32_t* Jp = (const int32_t*)data_slice__Jp;
                 const int32_t* Ji = (const int32_t*)data_slice__Ji;
                 const double*  Jx = (const double* )data_slice__Jx;
                 double* out       = (      double* )data_slice__output;

                 for(int i=0; i<Nx*Nx; i++)
                     out[i] = 0.0;

                 for(int irow=0; irow<*Nleading_rows_J; irow++)
                 {
                     double jta[Nx];
                     for(int i=0; i<Nx; i++)
                         jta[i] = 0.0;

                     for(int32_t i = Jp[irow]; i < Jp[irow+1]; i++)
                     {
                         int32_t icol = Ji[i];
                         double x     = Jx[i];

                         for(int j=0; j<Nx; j++)
                             jta[j] += A[icol + j*Nstate] * x;
                     }
                     for(int i=0; i<Nx; i++)
                     {
                         out[i*Nx + i] += jta[i]*jta[i];
                         for(int j=i+1; j<Nx; j++)
                         {
                             out[i*Nx + j] += jta[i]*jta[j];
                             out[j*Nx + i] += jta[i]*jta[j];
                         }
                     }
                 }
                 return true;

}
#undef item__output
#undef ctype__output
#undef item__A
#undef ctype__A
#undef item__Jp
#undef ctype__Jp
#undef item__Ji
#undef ctype__Ji
#undef item__Jx
#undef ctype__Jx
#define ARGUMENTS(_) \
  _(A) \
  _(Jp) \
  _(Ji) \
  _(Jx)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___A_Jt_J_At__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___A_Jt_J_At__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___A_Jt_J_At(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___A_Jt_J_At__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___A_Jt_J_At__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* Nleading_rows_J __attribute__((unused)),___A_Jt_J_At__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int Nleading_rows_J = -1;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "Nleading_rows_J",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "i" ":bindings_npsp._A_Jt_J_At",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &Nleading_rows_J,
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_A[2] = {-1,-2};
    const npy_intp PROTOTYPE_Jp[1] = {-3};
    const npy_intp PROTOTYPE_Ji[1] = {-4};
    const npy_intp PROTOTYPE_Jx[1] = {-4};
    const npy_intp PROTOTYPE_output[2] = {-1,-1};
    int Ndims_named = 4;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,5,5,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3,t4, i)                   \
        else if( __pywrap___A_Jt_J_At__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,t4,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___A_Jt_J_At__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,int32,int32,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,int32,int32,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___A_Jt_J_At__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &Nleading_rows_J,cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  &Nleading_rows_J,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___A_Jt_J_At__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  &Nleading_rows_J,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___A_Jt_J_At__slice failed!");
                goto done;
            }

        } while(__pywrap___A_Jt_J_At__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__A
#undef CHECK_CONTIGUOUS__A
#undef CHECK_CONTIGUOUS_AND_SETERROR__A
#undef _CHECK_CONTIGUOUS__Jp
#undef CHECK_CONTIGUOUS__Jp
#undef CHECK_CONTIGUOUS_AND_SETERROR__Jp
#undef _CHECK_CONTIGUOUS__Ji
#undef CHECK_CONTIGUOUS__Ji
#undef CHECK_CONTIGUOUS_AND_SETERROR__Ji
#undef _CHECK_CONTIGUOUS__Jx
#undef CHECK_CONTIGUOUS__Jx
#undef CHECK_CONTIGUOUS_AND_SETERROR__Jx

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _A_Jt_J_At__2

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__A(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__A; i++)                               \
      if(dims_full__A[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__A; i--)                       \
      {                                                                 \
          if(strides_slice__A[i+Ndims_slice__A] != sizeof_element__A*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'A' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__A[i+Ndims_slice__A];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__A()              _CHECK_CONTIGUOUS__A(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__A() _CHECK_CONTIGUOUS__A(true)


#define _CHECK_CONTIGUOUS__Jp(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__Jp; i++)                               \
      if(dims_full__Jp[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Jp; i--)                       \
      {                                                                 \
          if(strides_slice__Jp[i+Ndims_slice__Jp] != sizeof_element__Jp*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Jp' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Jp[i+Ndims_slice__Jp];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Jp()              _CHECK_CONTIGUOUS__Jp(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Jp() _CHECK_CONTIGUOUS__Jp(true)


#define _CHECK_CONTIGUOUS__Ji(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__Ji; i++)                               \
      if(dims_full__Ji[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Ji; i--)                       \
      {                                                                 \
          if(strides_slice__Ji[i+Ndims_slice__Ji] != sizeof_element__Ji*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Ji' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Ji[i+Ndims_slice__Ji];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Ji()              _CHECK_CONTIGUOUS__Ji(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Ji() _CHECK_CONTIGUOUS__Ji(true)


#define _CHECK_CONTIGUOUS__Jx(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__Jx; i++)                               \
      if(dims_full__Jx[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Jx; i--)                       \
      {                                                                 \
          if(strides_slice__Jx[i+Ndims_slice__Jx] != sizeof_element__Jx*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Jx' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Jx[i+Ndims_slice__Jx];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Jx()              _CHECK_CONTIGUOUS__Jx(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Jx() _CHECK_CONTIGUOUS__Jx(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__A() && CHECK_CONTIGUOUS__Jp() && CHECK_CONTIGUOUS__Ji() && CHECK_CONTIGUOUS__Jx()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__A() && CHECK_CONTIGUOUS_AND_SETERROR__Jp() && CHECK_CONTIGUOUS_AND_SETERROR__Ji() && CHECK_CONTIGUOUS_AND_SETERROR__Jx()

typedef struct {  } ___A_Jt_J_At__2__cookie_t;

static
bool ___A_Jt_J_At__2__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__A __attribute__((unused)),
  const npy_intp* dims_full__A __attribute__((unused)),
  const npy_intp* strides_full__A __attribute__((unused)),
  const int Ndims_slice__A __attribute__((unused)),
  const npy_intp* dims_slice__A __attribute__((unused)),
  const npy_intp* strides_slice__A __attribute__((unused)),
  npy_intp sizeof_element__A __attribute__((unused)),
  void* data__A __attribute__((unused)),
  const int Ndims_full__Jp __attribute__((unused)),
  const npy_intp* dims_full__Jp __attribute__((unused)),
  const npy_intp* strides_full__Jp __attribute__((unused)),
  const int Ndims_slice__Jp __attribute__((unused)),
  const npy_intp* dims_slice__Jp __attribute__((unused)),
  const npy_intp* strides_slice__Jp __attribute__((unused)),
  npy_intp sizeof_element__Jp __attribute__((unused)),
  void* data__Jp __attribute__((unused)),
  const int Ndims_full__Ji __attribute__((unused)),
  const npy_intp* dims_full__Ji __attribute__((unused)),
  const npy_intp* strides_full__Ji __attribute__((unused)),
  const int Ndims_slice__Ji __attribute__((unused)),
  const npy_intp* dims_slice__Ji __attribute__((unused)),
  const npy_intp* strides_slice__Ji __attribute__((unused)),
  npy_intp sizeof_element__Ji __attribute__((unused)),
  void* data__Ji __attribute__((unused)),
  const int Ndims_full__Jx __attribute__((unused)),
  const npy_intp* dims_full__Jx __attribute__((unused)),
  const npy_intp* strides_full__Jx __attribute__((unused)),
  const int Ndims_slice__Jx __attribute__((unused)),
  const npy_intp* dims_slice__Jx __attribute__((unused)),
  const npy_intp* strides_slice__Jx __attribute__((unused)),
  npy_intp sizeof_element__Jx __attribute__((unused)),
  void* data__Jx __attribute__((unused)),
  const int* Nleading_rows_J __attribute__((unused)),
  ___A_Jt_J_At__2__cookie_t* cookie __attribute__((unused)))
{

            if(*Nleading_rows_J <= 0)
            {
                PyErr_Format(PyExc_RuntimeError,
                             "Nleading_rows_J must be passed, and must be > 0");
                return false;
            }
            return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_float64
#define item__output(__ivar0,__ivar1) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]+ (__ivar1)*strides_slice__output[1]))
#define ctype__A npy_int32
#define item__A(__ivar0,__ivar1) (*(ctype__A*)(data_slice__A + (__ivar0)*strides_slice__A[0]+ (__ivar1)*strides_slice__A[1]))
#define ctype__Jp npy_int32
#define item__Jp(__ivar0) (*(ctype__Jp*)(data_slice__Jp + (__ivar0)*strides_slice__Jp[0]))
#define ctype__Ji npy_float64
#define item__Ji(__ivar0) (*(ctype__Ji*)(data_slice__Ji + (__ivar0)*strides_slice__Ji[0]))
#define ctype__Jx npy_float64
#define item__Jx(__ivar0) (*(ctype__Jx*)(data_slice__Jx + (__ivar0)*strides_slice__Jx[0]))

static
bool ___A_Jt_J_At__2__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__A __attribute__((unused)),
  const npy_intp* dims_full__A __attribute__((unused)),
  const npy_intp* strides_full__A __attribute__((unused)),
  const int Ndims_slice__A __attribute__((unused)),
  const npy_intp* dims_slice__A __attribute__((unused)),
  const npy_intp* strides_slice__A __attribute__((unused)),
  npy_intp sizeof_element__A __attribute__((unused)),
  void* data_slice__A __attribute__((unused)),
  const int Ndims_full__Jp __attribute__((unused)),
  const npy_intp* dims_full__Jp __attribute__((unused)),
  const npy_intp* strides_full__Jp __attribute__((unused)),
  const int Ndims_slice__Jp __attribute__((unused)),
  const npy_intp* dims_slice__Jp __attribute__((unused)),
  const npy_intp* strides_slice__Jp __attribute__((unused)),
  npy_intp sizeof_element__Jp __attribute__((unused)),
  void* data_slice__Jp __attribute__((unused)),
  const int Ndims_full__Ji __attribute__((unused)),
  const npy_intp* dims_full__Ji __attribute__((unused)),
  const npy_intp* strides_full__Ji __attribute__((unused)),
  const int Ndims_slice__Ji __attribute__((unused)),
  const npy_intp* dims_slice__Ji __attribute__((unused)),
  const npy_intp* strides_slice__Ji __attribute__((unused)),
  npy_intp sizeof_element__Ji __attribute__((unused)),
  void* data_slice__Ji __attribute__((unused)),
  const int Ndims_full__Jx __attribute__((unused)),
  const npy_intp* dims_full__Jx __attribute__((unused)),
  const npy_intp* strides_full__Jx __attribute__((unused)),
  const int Ndims_slice__Jx __attribute__((unused)),
  const npy_intp* dims_slice__Jx __attribute__((unused)),
  const npy_intp* strides_slice__Jx __attribute__((unused)),
  npy_intp sizeof_element__Jx __attribute__((unused)),
  void* data_slice__Jx __attribute__((unused)),
  const int* Nleading_rows_J __attribute__((unused)),
  ___A_Jt_J_At__2__cookie_t* cookie __attribute__((unused)))
{


                 // I'm computing A Jt J At = sum(outer(ja,ja)) where ja is each
                 // row of matmult(J,At). Rows of matmult(J,At) are
                 // matmult(jt,At) where jt are rows of J. So the logic is:
                 //
                 //   For each row jt of J:
                 //     jta = matmult(jt,At); // jta has shape (Nx,)
                 //     accumulate( outer(jta,jta) )


                 const int32_t Nx     = 2; // dims_slice__A[0] will always be 2
                 const int32_t Nstate = dims_slice__A[1];

                 const double*   A = (const double* )data_slice__A;
                 const int32_t* Jp = (const int32_t*)data_slice__Jp;
                 const int32_t* Ji = (const int32_t*)data_slice__Ji;
                 const double*  Jx = (const double* )data_slice__Jx;
                 double* out       = (      double* )data_slice__output;

                 for(int i=0; i<Nx*Nx; i++)
                     out[i] = 0.0;

                 for(int irow=0; irow<*Nleading_rows_J; irow++)
                 {
                     double jta[2] = {};

                     for(int32_t i = Jp[irow]; i < Jp[irow+1]; i++)
                     {
                         int32_t icol = Ji[i];
                         double x     = Jx[i];

                         jta[0] += A[icol         ] * x;
                         jta[1] += A[icol + Nstate] * x;
                     }

                     out[0] += jta[0]*jta[0];
                     out[1] += jta[0]*jta[1];
                     out[3] += jta[1]*jta[1];
                 }

                 out[2] = out[1];

                 return true;

}
#undef item__output
#undef ctype__output
#undef item__A
#undef ctype__A
#undef item__Jp
#undef ctype__Jp
#undef item__Ji
#undef ctype__Ji
#undef item__Jx
#undef ctype__Jx
#define ARGUMENTS(_) \
  _(A) \
  _(Jp) \
  _(Ji) \
  _(Jx)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___A_Jt_J_At__2__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___A_Jt_J_At__2__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___A_Jt_J_At__2(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___A_Jt_J_At__2__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___A_Jt_J_At__2__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* Nleading_rows_J __attribute__((unused)),___A_Jt_J_At__2__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int Nleading_rows_J = -1;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "Nleading_rows_J",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "i" ":bindings_npsp._A_Jt_J_At__2",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &Nleading_rows_J,
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_A[2] = {2,-1};
    const npy_intp PROTOTYPE_Jp[1] = {-2};
    const npy_intp PROTOTYPE_Ji[1] = {-3};
    const npy_intp PROTOTYPE_Jx[1] = {-3};
    const npy_intp PROTOTYPE_output[2] = {2,2};
    int Ndims_named = 3;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,5,5,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3,t4, i)                   \
        else if( __pywrap___A_Jt_J_At__2__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,t4,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___A_Jt_J_At__2__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,int32,int32,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,int32,int32,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___A_Jt_J_At__2__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &Nleading_rows_J,cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  &Nleading_rows_J,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___A_Jt_J_At__2__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  &Nleading_rows_J,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___A_Jt_J_At__2__slice failed!");
                goto done;
            }

        } while(__pywrap___A_Jt_J_At__2__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__A
#undef CHECK_CONTIGUOUS__A
#undef CHECK_CONTIGUOUS_AND_SETERROR__A
#undef _CHECK_CONTIGUOUS__Jp
#undef CHECK_CONTIGUOUS__Jp
#undef CHECK_CONTIGUOUS_AND_SETERROR__Jp
#undef _CHECK_CONTIGUOUS__Ji
#undef CHECK_CONTIGUOUS__Ji
#undef CHECK_CONTIGUOUS_AND_SETERROR__Ji
#undef _CHECK_CONTIGUOUS__Jx
#undef CHECK_CONTIGUOUS__Jx
#undef CHECK_CONTIGUOUS_AND_SETERROR__Jx

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _Jt_x

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__Jp(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__Jp; i++)                               \
      if(dims_full__Jp[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Jp; i--)                       \
      {                                                                 \
          if(strides_slice__Jp[i+Ndims_slice__Jp] != sizeof_element__Jp*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Jp' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Jp[i+Ndims_slice__Jp];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Jp()              _CHECK_CONTIGUOUS__Jp(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Jp() _CHECK_CONTIGUOUS__Jp(true)


#define _CHECK_CONTIGUOUS__Ji(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__Ji; i++)                               \
      if(dims_full__Ji[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Ji; i--)                       \
      {                                                                 \
          if(strides_slice__Ji[i+Ndims_slice__Ji] != sizeof_element__Ji*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Ji' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Ji[i+Ndims_slice__Ji];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Ji()              _CHECK_CONTIGUOUS__Ji(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Ji() _CHECK_CONTIGUOUS__Ji(true)


#define _CHECK_CONTIGUOUS__Jx(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__Jx; i++)                               \
      if(dims_full__Jx[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Jx; i--)                       \
      {                                                                 \
          if(strides_slice__Jx[i+Ndims_slice__Jx] != sizeof_element__Jx*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Jx' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Jx[i+Ndims_slice__Jx];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Jx()              _CHECK_CONTIGUOUS__Jx(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Jx() _CHECK_CONTIGUOUS__Jx(true)


#define _CHECK_CONTIGUOUS__xt(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__xt; i++)                               \
      if(dims_full__xt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__xt; i--)                       \
      {                                                                 \
          if(strides_slice__xt[i+Ndims_slice__xt] != sizeof_element__xt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'xt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__xt[i+Ndims_slice__xt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__xt()              _CHECK_CONTIGUOUS__xt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__xt() _CHECK_CONTIGUOUS__xt(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__Jp() && CHECK_CONTIGUOUS__Ji() && CHECK_CONTIGUOUS__Jx() && CHECK_CONTIGUOUS__xt()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__Jp() && CHECK_CONTIGUOUS_AND_SETERROR__Ji() && CHECK_CONTIGUOUS_AND_SETERROR__Jx() && CHECK_CONTIGUOUS_AND_SETERROR__xt()

typedef struct {  } ___Jt_x__cookie_t;

static
bool ___Jt_x__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__Jp __attribute__((unused)),
  const npy_intp* dims_full__Jp __attribute__((unused)),
  const npy_intp* strides_full__Jp __attribute__((unused)),
  const int Ndims_slice__Jp __attribute__((unused)),
  const npy_intp* dims_slice__Jp __attribute__((unused)),
  const npy_intp* strides_slice__Jp __attribute__((unused)),
  npy_intp sizeof_element__Jp __attribute__((unused)),
  void* data__Jp __attribute__((unused)),
  const int Ndims_full__Ji __attribute__((unused)),
  const npy_intp* dims_full__Ji __attribute__((unused)),
  const npy_intp* strides_full__Ji __attribute__((unused)),
  const int Ndims_slice__Ji __attribute__((unused)),
  const npy_intp* dims_slice__Ji __attribute__((unused)),
  const npy_intp* strides_slice__Ji __attribute__((unused)),
  npy_intp sizeof_element__Ji __attribute__((unused)),
  void* data__Ji __attribute__((unused)),
  const int Ndims_full__Jx __attribute__((unused)),
  const npy_intp* dims_full__Jx __attribute__((unused)),
  const npy_intp* strides_full__Jx __attribute__((unused)),
  const int Ndims_slice__Jx __attribute__((unused)),
  const npy_intp* dims_slice__Jx __attribute__((unused)),
  const npy_intp* strides_slice__Jx __attribute__((unused)),
  npy_intp sizeof_element__Jx __attribute__((unused)),
  void* data__Jx __attribute__((unused)),
  const int Ndims_full__xt __attribute__((unused)),
  const npy_intp* dims_full__xt __attribute__((unused)),
  const npy_intp* strides_full__xt __attribute__((unused)),
  const int Ndims_slice__xt __attribute__((unused)),
  const npy_intp* dims_slice__xt __attribute__((unused)),
  const npy_intp* strides_slice__xt __attribute__((unused)),
  npy_intp sizeof_element__xt __attribute__((unused)),
  void* data__xt __attribute__((unused)),
  ___Jt_x__cookie_t* cookie __attribute__((unused)))
{

            int32_t Np    = dims_slice__Jp[0];
            int32_t Nrows = dims_slice__xt[0];
            if( Nrows != Np-1 )
            {
                PyErr_Format(PyExc_RuntimeError,
                             "len(xt) must match the number of rows in J");
                return false;
            }
            return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_int32
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__Jp npy_int32
#define item__Jp(__ivar0) (*(ctype__Jp*)(data_slice__Jp + (__ivar0)*strides_slice__Jp[0]))
#define ctype__Ji npy_float64
#define item__Ji(__ivar0) (*(ctype__Ji*)(data_slice__Ji + (__ivar0)*strides_slice__Ji[0]))
#define ctype__Jx npy_float64
#define item__Jx(__ivar0) (*(ctype__Jx*)(data_slice__Jx + (__ivar0)*strides_slice__Jx[0]))
#define ctype__xt npy_float64
#define item__xt(__ivar0) (*(ctype__xt*)(data_slice__xt + (__ivar0)*strides_slice__xt[0]))

static
bool ___Jt_x__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__Jp __attribute__((unused)),
  const npy_intp* dims_full__Jp __attribute__((unused)),
  const npy_intp* strides_full__Jp __attribute__((unused)),
  const int Ndims_slice__Jp __attribute__((unused)),
  const npy_intp* dims_slice__Jp __attribute__((unused)),
  const npy_intp* strides_slice__Jp __attribute__((unused)),
  npy_intp sizeof_element__Jp __attribute__((unused)),
  void* data_slice__Jp __attribute__((unused)),
  const int Ndims_full__Ji __attribute__((unused)),
  const npy_intp* dims_full__Ji __attribute__((unused)),
  const npy_intp* strides_full__Ji __attribute__((unused)),
  const int Ndims_slice__Ji __attribute__((unused)),
  const npy_intp* dims_slice__Ji __attribute__((unused)),
  const npy_intp* strides_slice__Ji __attribute__((unused)),
  npy_intp sizeof_element__Ji __attribute__((unused)),
  void* data_slice__Ji __attribute__((unused)),
  const int Ndims_full__Jx __attribute__((unused)),
  const npy_intp* dims_full__Jx __attribute__((unused)),
  const npy_intp* strides_full__Jx __attribute__((unused)),
  const int Ndims_slice__Jx __attribute__((unused)),
  const npy_intp* dims_slice__Jx __attribute__((unused)),
  const npy_intp* strides_slice__Jx __attribute__((unused)),
  npy_intp sizeof_element__Jx __attribute__((unused)),
  void* data_slice__Jx __attribute__((unused)),
  const int Ndims_full__xt __attribute__((unused)),
  const npy_intp* dims_full__xt __attribute__((unused)),
  const npy_intp* strides_full__xt __attribute__((unused)),
  const int Ndims_slice__xt __attribute__((unused)),
  const npy_intp* dims_slice__xt __attribute__((unused)),
  const npy_intp* strides_slice__xt __attribute__((unused)),
  npy_intp sizeof_element__xt __attribute__((unused)),
  void* data_slice__xt __attribute__((unused)),
  ___Jt_x__cookie_t* cookie __attribute__((unused)))
{


                 int32_t Np        = dims_slice__Jp[0];
                 const int32_t* Jp = (const int32_t*)data_slice__Jp;
                 const int32_t* Ji = (const int32_t*)data_slice__Ji;
                 const double*  Jx = (const double* )data_slice__Jx;

                 const double* x   = (const double* )data_slice__xt;


                 int32_t Ny = dims_slice__output[0];
                 double* y  = (double*)data_slice__output;

                 const int32_t Nrows = Np-1;
                 for(int i=0; i<Ny; i++)
                     y[i] = 0.0;

                 for(int irow=0; irow<Nrows; irow++)
                 {
                     for(int32_t i = Jp[irow]; i < Jp[irow+1]; i++)
                     {
                         int32_t icol = Ji[i];
                         double j     = Jx[i];

                         y[icol] += j*x[irow];
                     }
                 }
                 return true;

}
#undef item__output
#undef ctype__output
#undef item__Jp
#undef ctype__Jp
#undef item__Ji
#undef ctype__Ji
#undef item__Jx
#undef ctype__Jx
#undef item__xt
#undef ctype__xt
#define ARGUMENTS(_) \
  _(Jp) \
  _(Ji) \
  _(Jx) \
  _(xt)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___Jt_x__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___Jt_x__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___Jt_x(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___Jt_x__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___Jt_x__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___Jt_x__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp._Jt_x",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_Jp[1] = {-1};
    const npy_intp PROTOTYPE_Ji[1] = {-2};
    const npy_intp PROTOTYPE_Jx[1] = {-2};
    const npy_intp PROTOTYPE_xt[1] = {-3};
    const npy_intp PROTOTYPE_output[1] = {-4};
    int Ndims_named = 4;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(5,5,12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3,t4, i)                   \
        else if( __pywrap___Jt_x__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,t4,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___Jt_x__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: int32,int32,float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: int32,int32,float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___Jt_x__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___Jt_x__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___Jt_x__slice failed!");
                goto done;
            }

        } while(__pywrap___Jt_x__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__Jp
#undef CHECK_CONTIGUOUS__Jp
#undef CHECK_CONTIGUOUS_AND_SETERROR__Jp
#undef _CHECK_CONTIGUOUS__Ji
#undef CHECK_CONTIGUOUS__Ji
#undef CHECK_CONTIGUOUS_AND_SETERROR__Ji
#undef _CHECK_CONTIGUOUS__Jx
#undef CHECK_CONTIGUOUS__Jx
#undef CHECK_CONTIGUOUS_AND_SETERROR__Jx
#undef _CHECK_CONTIGUOUS__xt
#undef CHECK_CONTIGUOUS__xt
#undef CHECK_CONTIGUOUS_AND_SETERROR__xt

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   apply_homography

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__H(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__H; i++)                               \
      if(dims_full__H[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__H; i--)                       \
      {                                                                 \
          if(strides_slice__H[i+Ndims_slice__H] != sizeof_element__H*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'H' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__H[i+Ndims_slice__H];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__H()              _CHECK_CONTIGUOUS__H(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__H() _CHECK_CONTIGUOUS__H(true)


#define _CHECK_CONTIGUOUS__v(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__v; i++)                               \
      if(dims_full__v[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__v; i--)                       \
      {                                                                 \
          if(strides_slice__v[i+Ndims_slice__v] != sizeof_element__v*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'v' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__v[i+Ndims_slice__v];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__v()              _CHECK_CONTIGUOUS__v(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__v() _CHECK_CONTIGUOUS__v(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__H() && CHECK_CONTIGUOUS__v()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__H() && CHECK_CONTIGUOUS_AND_SETERROR__v()

typedef struct {  } __apply_homography__cookie_t;

static
bool __apply_homography__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__H __attribute__((unused)),
  const npy_intp* dims_full__H __attribute__((unused)),
  const npy_intp* strides_full__H __attribute__((unused)),
  const int Ndims_slice__H __attribute__((unused)),
  const npy_intp* dims_slice__H __attribute__((unused)),
  const npy_intp* strides_slice__H __attribute__((unused)),
  npy_intp sizeof_element__H __attribute__((unused)),
  void* data__H __attribute__((unused)),
  const int Ndims_full__v __attribute__((unused)),
  const npy_intp* dims_full__v __attribute__((unused)),
  const npy_intp* strides_full__v __attribute__((unused)),
  const int Ndims_slice__v __attribute__((unused)),
  const npy_intp* dims_slice__v __attribute__((unused)),
  const npy_intp* strides_slice__v __attribute__((unused)),
  npy_intp sizeof_element__v __attribute__((unused)),
  void* data__v __attribute__((unused)),
  __apply_homography__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__H npy_float64
#define item__H(__ivar0,__ivar1) (*(ctype__H*)(data_slice__H + (__ivar0)*strides_slice__H[0]+ (__ivar1)*strides_slice__H[1]))
#define ctype__v npy_float64
#define item__v(__ivar0) (*(ctype__v*)(data_slice__v + (__ivar0)*strides_slice__v[0]))

static
bool __apply_homography__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__H __attribute__((unused)),
  const npy_intp* dims_full__H __attribute__((unused)),
  const npy_intp* strides_full__H __attribute__((unused)),
  const int Ndims_slice__H __attribute__((unused)),
  const npy_intp* dims_slice__H __attribute__((unused)),
  const npy_intp* strides_slice__H __attribute__((unused)),
  npy_intp sizeof_element__H __attribute__((unused)),
  void* data_slice__H __attribute__((unused)),
  const int Ndims_full__v __attribute__((unused)),
  const npy_intp* dims_full__v __attribute__((unused)),
  const npy_intp* strides_full__v __attribute__((unused)),
  const int Ndims_slice__v __attribute__((unused)),
  const npy_intp* dims_slice__v __attribute__((unused)),
  const npy_intp* strides_slice__v __attribute__((unused)),
  npy_intp sizeof_element__v __attribute__((unused)),
  void* data_slice__v __attribute__((unused)),
  __apply_homography__cookie_t* cookie __attribute__((unused)))
{

    ctype__v xyz[3] = {
        item__H(0,0)*item__v(0) + item__H(0,1)*item__v(1) + item__H(0,2),
        item__H(1,0)*item__v(0) + item__H(1,1)*item__v(1) + item__H(1,2),
        item__H(2,0)*item__v(0) + item__H(2,1)*item__v(1) + item__H(2,2)
     };
     item__output(0) = xyz[0]/xyz[2];
     item__output(1) = xyz[1]/xyz[2];
     return true;

}
#undef item__output
#undef ctype__output
#undef item__H
#undef ctype__H
#undef item__v
#undef ctype__v

#define ctype__output npy_float32
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__H npy_float32
#define item__H(__ivar0,__ivar1) (*(ctype__H*)(data_slice__H + (__ivar0)*strides_slice__H[0]+ (__ivar1)*strides_slice__H[1]))
#define ctype__v npy_float32
#define item__v(__ivar0) (*(ctype__v*)(data_slice__v + (__ivar0)*strides_slice__v[0]))

static
bool __apply_homography__1__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__H __attribute__((unused)),
  const npy_intp* dims_full__H __attribute__((unused)),
  const npy_intp* strides_full__H __attribute__((unused)),
  const int Ndims_slice__H __attribute__((unused)),
  const npy_intp* dims_slice__H __attribute__((unused)),
  const npy_intp* strides_slice__H __attribute__((unused)),
  npy_intp sizeof_element__H __attribute__((unused)),
  void* data_slice__H __attribute__((unused)),
  const int Ndims_full__v __attribute__((unused)),
  const npy_intp* dims_full__v __attribute__((unused)),
  const npy_intp* strides_full__v __attribute__((unused)),
  const int Ndims_slice__v __attribute__((unused)),
  const npy_intp* dims_slice__v __attribute__((unused)),
  const npy_intp* strides_slice__v __attribute__((unused)),
  npy_intp sizeof_element__v __attribute__((unused)),
  void* data_slice__v __attribute__((unused)),
  __apply_homography__cookie_t* cookie __attribute__((unused)))
{

    ctype__v xyz[3] = {
        item__H(0,0)*item__v(0) + item__H(0,1)*item__v(1) + item__H(0,2),
        item__H(1,0)*item__v(0) + item__H(1,1)*item__v(1) + item__H(1,2),
        item__H(2,0)*item__v(0) + item__H(2,1)*item__v(1) + item__H(2,2)
     };
     item__output(0) = xyz[0]/xyz[2];
     item__output(1) = xyz[1]/xyz[2];
     return true;

}
#undef item__output
#undef ctype__output
#undef item__H
#undef ctype__H
#undef item__v
#undef ctype__v
#define ARGUMENTS(_) \
  _(H) \
  _(v)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap__apply_homography__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap__apply_homography__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap__apply_homography(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    __apply_homography__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    __apply_homography__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) __apply_homography__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    ;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O"  ":bindings_npsp.apply_homography",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_H[2] = {3,3};
    const npy_intp PROTOTYPE_v[1] = {2};
    const npy_intp PROTOTYPE_output[1] = {2};
    int Ndims_named = 0;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,0) _(11,11,11,1)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap__apply_homography__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = __apply_homography__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n" "  (inputs: float32,float32   outputs: float32)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64   outputs: float64)\n" "  (inputs: float32,float32   outputs: float32)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! __apply_homography__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "__apply_homography__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "__apply_homography__slice failed!");
                goto done;
            }

        } while(__pywrap__apply_homography__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__H
#undef CHECK_CONTIGUOUS__H
#undef CHECK_CONTIGUOUS_AND_SETERROR__H
#undef _CHECK_CONTIGUOUS__v
#undef CHECK_CONTIGUOUS__v
#undef CHECK_CONTIGUOUS_AND_SETERROR__v

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c
///////// for function   _stereo_range_sparse

#define _CHECK_CONTIGUOUS__output(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__output; i++)                               \
      if(dims_full__output[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__output; i--)                       \
      {                                                                 \
          if(strides_slice__output[i+Ndims_slice__output] != sizeof_element__output*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'output' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__output[i+Ndims_slice__output];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__output()              _CHECK_CONTIGUOUS__output(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__output() _CHECK_CONTIGUOUS__output(true)


#define _CHECK_CONTIGUOUS__disparity(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__disparity; i++)                               \
      if(dims_full__disparity[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__disparity; i--)                       \
      {                                                                 \
          if(strides_slice__disparity[i+Ndims_slice__disparity] != sizeof_element__disparity*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'disparity' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__disparity[i+Ndims_slice__disparity];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__disparity()              _CHECK_CONTIGUOUS__disparity(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__disparity() _CHECK_CONTIGUOUS__disparity(true)


#define _CHECK_CONTIGUOUS__qrect0(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__qrect0; i++)                               \
      if(dims_full__qrect0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__qrect0; i--)                       \
      {                                                                 \
          if(strides_slice__qrect0[i+Ndims_slice__qrect0] != sizeof_element__qrect0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'qrect0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__qrect0[i+Ndims_slice__qrect0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__qrect0()              _CHECK_CONTIGUOUS__qrect0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__qrect0() _CHECK_CONTIGUOUS__qrect0(true)


#define _CHECK_CONTIGUOUS__disparity_min(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__disparity_min; i++)                               \
      if(dims_full__disparity_min[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__disparity_min; i--)                       \
      {                                                                 \
          if(strides_slice__disparity_min[i+Ndims_slice__disparity_min] != sizeof_element__disparity_min*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'disparity_min' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__disparity_min[i+Ndims_slice__disparity_min];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__disparity_min()              _CHECK_CONTIGUOUS__disparity_min(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__disparity_min() _CHECK_CONTIGUOUS__disparity_min(true)


#define _CHECK_CONTIGUOUS__disparity_max(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__disparity_max; i++)                               \
      if(dims_full__disparity_max[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__disparity_max; i--)                       \
      {                                                                 \
          if(strides_slice__disparity_max[i+Ndims_slice__disparity_max] != sizeof_element__disparity_max*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'disparity_max' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__disparity_max[i+Ndims_slice__disparity_max];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__disparity_max()              _CHECK_CONTIGUOUS__disparity_max(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__disparity_max() _CHECK_CONTIGUOUS__disparity_max(true)


#define _CHECK_CONTIGUOUS__fxycxy_rectified(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__fxycxy_rectified; i++)                               \
      if(dims_full__fxycxy_rectified[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__fxycxy_rectified; i--)                       \
      {                                                                 \
          if(strides_slice__fxycxy_rectified[i+Ndims_slice__fxycxy_rectified] != sizeof_element__fxycxy_rectified*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'fxycxy_rectified' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__fxycxy_rectified[i+Ndims_slice__fxycxy_rectified];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__fxycxy_rectified()              _CHECK_CONTIGUOUS__fxycxy_rectified(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__fxycxy_rectified() _CHECK_CONTIGUOUS__fxycxy_rectified(true)


#define _CHECK_CONTIGUOUS__baseline(seterror)                             \
({                                                                      \
  bool result     = true;                                               \
  bool have_dim_0 = false;                                              \
  /* If I have no data, just call the thing contiguous. This is useful */ \
  /* because np.ascontiguousarray doesn't set contiguous alignment */   \
  /* for empty arrays */                                                \
  for(int i=0; i<Ndims_full__baseline; i++)                               \
      if(dims_full__baseline[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__baseline; i--)                       \
      {                                                                 \
          if(strides_slice__baseline[i+Ndims_slice__baseline] != sizeof_element__baseline*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'baseline' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__baseline[i+Ndims_slice__baseline];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__baseline()              _CHECK_CONTIGUOUS__baseline(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__baseline() _CHECK_CONTIGUOUS__baseline(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__disparity() && CHECK_CONTIGUOUS__qrect0() && CHECK_CONTIGUOUS__disparity_min() && CHECK_CONTIGUOUS__disparity_max() && CHECK_CONTIGUOUS__fxycxy_rectified() && CHECK_CONTIGUOUS__baseline()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__disparity() && CHECK_CONTIGUOUS_AND_SETERROR__qrect0() && CHECK_CONTIGUOUS_AND_SETERROR__disparity_min() && CHECK_CONTIGUOUS_AND_SETERROR__disparity_max() && CHECK_CONTIGUOUS_AND_SETERROR__fxycxy_rectified() && CHECK_CONTIGUOUS_AND_SETERROR__baseline()

typedef struct { 
              mrcal_lensmodel_type_t type;
             } ___stereo_range_sparse__cookie_t;

static
bool ___stereo_range_sparse__validate(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data__output __attribute__((unused)),
  const int Ndims_full__disparity __attribute__((unused)),
  const npy_intp* dims_full__disparity __attribute__((unused)),
  const npy_intp* strides_full__disparity __attribute__((unused)),
  const int Ndims_slice__disparity __attribute__((unused)),
  const npy_intp* dims_slice__disparity __attribute__((unused)),
  const npy_intp* strides_slice__disparity __attribute__((unused)),
  npy_intp sizeof_element__disparity __attribute__((unused)),
  void* data__disparity __attribute__((unused)),
  const int Ndims_full__qrect0 __attribute__((unused)),
  const npy_intp* dims_full__qrect0 __attribute__((unused)),
  const npy_intp* strides_full__qrect0 __attribute__((unused)),
  const int Ndims_slice__qrect0 __attribute__((unused)),
  const npy_intp* dims_slice__qrect0 __attribute__((unused)),
  const npy_intp* strides_slice__qrect0 __attribute__((unused)),
  npy_intp sizeof_element__qrect0 __attribute__((unused)),
  void* data__qrect0 __attribute__((unused)),
  const int Ndims_full__disparity_min __attribute__((unused)),
  const npy_intp* dims_full__disparity_min __attribute__((unused)),
  const npy_intp* strides_full__disparity_min __attribute__((unused)),
  const int Ndims_slice__disparity_min __attribute__((unused)),
  const npy_intp* dims_slice__disparity_min __attribute__((unused)),
  const npy_intp* strides_slice__disparity_min __attribute__((unused)),
  npy_intp sizeof_element__disparity_min __attribute__((unused)),
  void* data__disparity_min __attribute__((unused)),
  const int Ndims_full__disparity_max __attribute__((unused)),
  const npy_intp* dims_full__disparity_max __attribute__((unused)),
  const npy_intp* strides_full__disparity_max __attribute__((unused)),
  const int Ndims_slice__disparity_max __attribute__((unused)),
  const npy_intp* dims_slice__disparity_max __attribute__((unused)),
  const npy_intp* strides_slice__disparity_max __attribute__((unused)),
  npy_intp sizeof_element__disparity_max __attribute__((unused)),
  void* data__disparity_max __attribute__((unused)),
  const int Ndims_full__fxycxy_rectified __attribute__((unused)),
  const npy_intp* dims_full__fxycxy_rectified __attribute__((unused)),
  const npy_intp* strides_full__fxycxy_rectified __attribute__((unused)),
  const int Ndims_slice__fxycxy_rectified __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy_rectified __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy_rectified __attribute__((unused)),
  npy_intp sizeof_element__fxycxy_rectified __attribute__((unused)),
  void* data__fxycxy_rectified __attribute__((unused)),
  const int Ndims_full__baseline __attribute__((unused)),
  const npy_intp* dims_full__baseline __attribute__((unused)),
  const npy_intp* strides_full__baseline __attribute__((unused)),
  const int Ndims_slice__baseline __attribute__((unused)),
  const npy_intp* dims_slice__baseline __attribute__((unused)),
  const npy_intp* strides_slice__baseline __attribute__((unused)),
  npy_intp sizeof_element__baseline __attribute__((unused)),
  void* data__baseline __attribute__((unused)),
  const char* rectification_model_type __attribute__((unused)),
  ___stereo_range_sparse__cookie_t* cookie __attribute__((unused)))
{


              cookie->type = mrcal_lensmodel_type_from_name(rectification_model_type);
              // the mrcal_stereo_range_...() functions will check to make sure
              // the type is valid

              return CHECK_CONTIGUOUS_AND_SETERROR_ALL();
}

#define ctype__output npy_float64
#define item__output(__ivar0) (*(ctype__output*)(data_slice__output + (__ivar0)*strides_slice__output[0]))
#define ctype__disparity npy_float64
#define item__disparity(__ivar0) (*(ctype__disparity*)(data_slice__disparity + (__ivar0)*strides_slice__disparity[0]))
#define ctype__qrect0 npy_float64
#define item__qrect0(__ivar0,__ivar1) (*(ctype__qrect0*)(data_slice__qrect0 + (__ivar0)*strides_slice__qrect0[0]+ (__ivar1)*strides_slice__qrect0[1]))
#define ctype__disparity_min npy_float64
#define item__disparity_min() (*(ctype__disparity_min*)(data_slice__disparity_min ))
#define ctype__disparity_max npy_float64
#define item__disparity_max() (*(ctype__disparity_max*)(data_slice__disparity_max ))
#define ctype__fxycxy_rectified npy_float64
#define item__fxycxy_rectified(__ivar0) (*(ctype__fxycxy_rectified*)(data_slice__fxycxy_rectified + (__ivar0)*strides_slice__fxycxy_rectified[0]))
#define ctype__baseline npy_float64
#define item__baseline() (*(ctype__baseline*)(data_slice__baseline ))

static
bool ___stereo_range_sparse__0__slice(
  const int Ndims_full__output __attribute__((unused)),
  const npy_intp* dims_full__output __attribute__((unused)),
  const npy_intp* strides_full__output __attribute__((unused)),
  const int Ndims_slice__output __attribute__((unused)),
  const npy_intp* dims_slice__output __attribute__((unused)),
  const npy_intp* strides_slice__output __attribute__((unused)),
  npy_intp sizeof_element__output __attribute__((unused)),
  void* data_slice__output __attribute__((unused)),
  const int Ndims_full__disparity __attribute__((unused)),
  const npy_intp* dims_full__disparity __attribute__((unused)),
  const npy_intp* strides_full__disparity __attribute__((unused)),
  const int Ndims_slice__disparity __attribute__((unused)),
  const npy_intp* dims_slice__disparity __attribute__((unused)),
  const npy_intp* strides_slice__disparity __attribute__((unused)),
  npy_intp sizeof_element__disparity __attribute__((unused)),
  void* data_slice__disparity __attribute__((unused)),
  const int Ndims_full__qrect0 __attribute__((unused)),
  const npy_intp* dims_full__qrect0 __attribute__((unused)),
  const npy_intp* strides_full__qrect0 __attribute__((unused)),
  const int Ndims_slice__qrect0 __attribute__((unused)),
  const npy_intp* dims_slice__qrect0 __attribute__((unused)),
  const npy_intp* strides_slice__qrect0 __attribute__((unused)),
  npy_intp sizeof_element__qrect0 __attribute__((unused)),
  void* data_slice__qrect0 __attribute__((unused)),
  const int Ndims_full__disparity_min __attribute__((unused)),
  const npy_intp* dims_full__disparity_min __attribute__((unused)),
  const npy_intp* strides_full__disparity_min __attribute__((unused)),
  const int Ndims_slice__disparity_min __attribute__((unused)),
  const npy_intp* dims_slice__disparity_min __attribute__((unused)),
  const npy_intp* strides_slice__disparity_min __attribute__((unused)),
  npy_intp sizeof_element__disparity_min __attribute__((unused)),
  void* data_slice__disparity_min __attribute__((unused)),
  const int Ndims_full__disparity_max __attribute__((unused)),
  const npy_intp* dims_full__disparity_max __attribute__((unused)),
  const npy_intp* strides_full__disparity_max __attribute__((unused)),
  const int Ndims_slice__disparity_max __attribute__((unused)),
  const npy_intp* dims_slice__disparity_max __attribute__((unused)),
  const npy_intp* strides_slice__disparity_max __attribute__((unused)),
  npy_intp sizeof_element__disparity_max __attribute__((unused)),
  void* data_slice__disparity_max __attribute__((unused)),
  const int Ndims_full__fxycxy_rectified __attribute__((unused)),
  const npy_intp* dims_full__fxycxy_rectified __attribute__((unused)),
  const npy_intp* strides_full__fxycxy_rectified __attribute__((unused)),
  const int Ndims_slice__fxycxy_rectified __attribute__((unused)),
  const npy_intp* dims_slice__fxycxy_rectified __attribute__((unused)),
  const npy_intp* strides_slice__fxycxy_rectified __attribute__((unused)),
  npy_intp sizeof_element__fxycxy_rectified __attribute__((unused)),
  void* data_slice__fxycxy_rectified __attribute__((unused)),
  const int Ndims_full__baseline __attribute__((unused)),
  const npy_intp* dims_full__baseline __attribute__((unused)),
  const npy_intp* strides_full__baseline __attribute__((unused)),
  const int Ndims_slice__baseline __attribute__((unused)),
  const npy_intp* dims_slice__baseline __attribute__((unused)),
  const npy_intp* strides_slice__baseline __attribute__((unused)),
  npy_intp sizeof_element__baseline __attribute__((unused)),
  void* data_slice__baseline __attribute__((unused)),
  const char* rectification_model_type __attribute__((unused)),
  ___stereo_range_sparse__cookie_t* cookie __attribute__((unused)))
{


                 const int N = dims_slice__output[0];

                 return mrcal_stereo_range_sparse(// output
                   (double*)data_slice__output,

                   // input
                   (double*)data_slice__disparity,
                   (const mrcal_point2_t*)data_slice__qrect0,
                   N,

                   *(double*)data_slice__disparity_min,
                   *(double*)data_slice__disparity_max,

                   // models_rectified
                   cookie->type,
                   (double*)data_slice__fxycxy_rectified,
                   *(double*)data_slice__baseline);

}
#undef item__output
#undef ctype__output
#undef item__disparity
#undef ctype__disparity
#undef item__qrect0
#undef ctype__qrect0
#undef item__disparity_min
#undef ctype__disparity_min
#undef item__disparity_max
#undef ctype__disparity_max
#undef item__fxycxy_rectified
#undef ctype__fxycxy_rectified
#undef item__baseline
#undef ctype__baseline
#define ARGUMENTS(_) \
  _(disparity) \
  _(qrect0) \
  _(disparity_min) \
  _(disparity_max) \
  _(fxycxy_rectified) \
  _(baseline)

#define OUTPUTS(_) \
  _(output)

#define ARG_DEFINE(     name) PyArrayObject* __py__ ## name = NULL;
#define ARGLIST_DECLARE(name) PyArrayObject* __py__ ## name,
#define ARGLIST_CALL(   name) __py__ ## name,

#define ARGLIST_SELECTED_TYPENUM_PTR_DECLARE(name) int* selected_typenum__ ## name,
#define ARGLIST_SELECTED_TYPENUM_PTR_CALL(   name) &selected_typenum__ ## name,


#define SLICE_ARG(name)                         \
                                                \
    const int       Ndims_full__     ## name,   \
    const npy_intp* dims_full__      ## name,   \
    const npy_intp* strides_full__   ## name,   \
                                                \
    const int       Ndims_slice__    ## name,   \
    const npy_intp* dims_slice__     ## name,   \
    const npy_intp* strides_slice__  ## name,   \
                                                \
    npy_intp        sizeof_element__ ## name,   \
    void*           data_slice__     ## name,


static
bool __pywrap___stereo_range_sparse__next(int* idims, const npy_intp* Ndims, int N)
{
    for(int i = N-1; i>=0; i--)
    {
        if(++idims[i] < Ndims[i])
            return true;
        idims[i] = 0;
    }
    return false;
}

#define TYPE_MATCHES_ARGLIST(name) int typenum__ ## name,
bool __pywrap___stereo_range_sparse__type_matches(
                  ARGUMENTS(TYPE_MATCHES_ARGLIST)
                  OUTPUTS(  TYPE_MATCHES_ARGLIST)
                  ARGUMENTS(ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_DECLARE)
                  OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_DECLARE)
                  int dummy __attribute__((unused)) )
{

#define SET_SELECTED_TYPENUM_OUTPUT(name) *selected_typenum__ ## name = typenum__ ## name;
#define TYPE_MATCHES(name)                                              \
    && ( __py__ ## name == NULL ||                              \
      (PyObject*)__py__ ## name == Py_None ||                   \
      PyArray_DESCR(__py__ ## name)->type_num == typenum__ ## name )

    if(true ARGUMENTS(TYPE_MATCHES) OUTPUTS(TYPE_MATCHES))
    {
        /* all arguments match this typeset! */
        OUTPUTS(SET_SELECTED_TYPENUM_OUTPUT);
        return true;
    }
    return false;
}
#undef SET_SELECTED_TYPENUM_OUTPUT
#undef TYPE_MATCHES
#undef TYPE_MATCHES_ARGLIST


static
PyObject* __pywrap___stereo_range_sparse(PyObject* NPY_UNUSED(self),
                                    PyObject* args,
                                    PyObject* kwargs)
{
    // The cookie we compute BEFORE computing any slices. This is available to
    // the slice-computation function to do whatever they please. I initialize
    // the cookie to all-zeros. If any cleanup is needed, the COOKIE_CLEANUP
    // code at the end of this function should include an "inited" flag in the
    // cookie in order to know whether the cookie was inited in the first place,
    // and whether any cleanup is actually required
    ___stereo_range_sparse__cookie_t  _cookie = {};
    // I'd like to access the "cookie" here in a way identical to how I access
    // it inside the functions, so it must be a cookie_t* cookie
    ___stereo_range_sparse__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const char* rectification_model_type __attribute__((unused)),___stereo_range_sparse__cookie_t* cookie __attribute__((unused)));


    PyObject* __py__result__    = NULL;
    PyObject* __py__output__arg = NULL;

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    const char* rectification_model_type = NULL;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "rectification_model_type",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "s" ":bindings_npsp._stereo_range_sparse",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &rectification_model_type,
                                     NULL))
        goto done;

    // parse_dims() is a helper function to evaluate a given list of arguments
    // in respect to a given broadcasting prototype. This function will flag any
    // errors in the dimensionality of the inputs. If no errors are detected, it
    // returns

    //   dims_extra,dims_named

    // where

    //   dims_extra is the outer dimensions of the broadcast
    //   dims_named is the values of the named dimensions


    // First I initialize dims_extra: the array containing the broadcasted
    // slices. Each argument calls for some number of extra dimensions, and the
    // overall array is as large as the biggest one of those

    const npy_intp PROTOTYPE_disparity[1] = {-1};
    const npy_intp PROTOTYPE_qrect0[2] = {-1,2};
    const npy_intp PROTOTYPE_disparity_min[0] = {};
    const npy_intp PROTOTYPE_disparity_max[0] = {};
    const npy_intp PROTOTYPE_fxycxy_rectified[1] = {4};
    const npy_intp PROTOTYPE_baseline[0] = {};
    const npy_intp PROTOTYPE_output[1] = {-1};
    int Ndims_named = 1;
;

    int populate_output_tuple__i = -1;
    if(__py__output__arg == Py_None) __py__output__arg = NULL;
    if(__py__output__arg == NULL)
    {
        // One output, not given. Leave everything at NULL (it already is).
        // Will be allocated later
    }
    else
    {
        // Argument given. Treat it as an array
        Py_INCREF(__py__output__arg);
        if(!PyArray_Check(__py__output__arg))
        {
            PyErr_SetString(PyExc_RuntimeError,
                            "Could not interpret given argument as a numpy array");
            goto done;
        }
        __py__output = (PyArrayObject*)__py__output__arg;
        Py_INCREF(__py__output);
    }
;

    // At this point each output array is either NULL or a PyObject with a
    // reference. In all cases, Py_XDECREF() should be done at the end. If we
    // have multiple outputs, either the output sequence is already filled-in
    // with valid arrays (if they were passed-in; I just checked in
    // UNPACK_OUTPUTS) or the output tuple is full of blank spaces, and each
    // output is NULL (if I just made a new tuple). In the latter case I'll fill
    // it in later
    //
    // The output argument in __py__output__arg is NULL if we have a single
    // output that's not yet allocated. Otherwise it has a reference also, so it
    // should be PY_XDECREF() at the end. This __py__output__arg is what we
    // should return, unless it's NULL or Py_None. In that case we need to
    // allocate a new array, and return THAT

    {
        // I process the types. The output arrays may not have been created yet,
        // in which case I just let NULL pass, and ignore the type. I'll make
        // new arrays later, and those will have the right type
#define DEFINE_OUTPUT_TYPENUM(name) int selected_typenum__ ## name;
        OUTPUTS(DEFINE_OUTPUT_TYPENUM);
#undef DEFINE_OUTPUT_TYPENUM
        slice_function_t* slice_function = NULL;

#define TYPESETS(_) \
        _(12,12,12,12,12,12,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3,t4,t5,t6, i)                   \
        else if( __pywrap___stereo_range_sparse__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,t4,t5,t6,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___stereo_range_sparse__ ## i ## __slice;       \
        }

        if(0) ;
        TYPESETS(TYPESET_MATCHES)
        else
        {

#if PY_MAJOR_VERSION == 3

#define INPUT_PERCENT_S(name) "%S,"
#define INPUT_TYPEOBJ(name) ,(((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) ? \
                              (PyObject*)PyArray_DESCR(__py__ ## name)->typeobj : (PyObject*)Py_None)

            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64,float64,float64,float64,float64   outputs: float64)\n"
                         "instead I got types (inputs: " ARGUMENTS(INPUT_PERCENT_S) ")"
                         "   outputs: (" OUTPUTS(INPUT_PERCENT_S) ")\n"
                         "None in an output is not an error: a new array of the right type will be created"
                         ARGUMENTS(INPUT_TYPEOBJ)
                         OUTPUTS(INPUT_TYPEOBJ) );

#else
            ////////// python2 doesn't support %S
            PyErr_Format(PyExc_RuntimeError,
                         "The set of input and output types must correspond to one of these sets:\n"
                         "  (inputs: float64,float64,float64,float64,float64,float64   outputs: float64)\n");
#endif

            goto done;
        }
#undef TYPESETS
#undef TYPESET_MATCHES


        // Now deal with dimensionality

        // It's possible for my arguments (and the output) to have fewer
        // dimensions than required by the prototype, and still pass all the
        // dimensionality checks, assuming implied leading dimensions of length
        // 1. For instance I could receive a scalar where a ('n',) dimension is
        // expected, or a ('n',) vector where an ('m','n') array is expected. I
        // initially handle this with Ndims_extra<0 for those arguments and then
        // later, I make copies with actual "1" values in place. I do that because:
        //
        // 1. I want to support the above-described case where implicit leading
        //    length-1 dimensions are used
        //
        // 2. I want to support new named-dimensions in the outputs, pulled from
        //    the in-place arrays
        //
        // #2 requires partial processing of the outputs before they're all
        // guaranteed to exist. So I can't allocate temporary __dims__##name and
        // __strides__##name arrays on the stack: I don't know how big they are
        // yet. But I need explicit dimensions in memory to pass to the
        // validation and slice callbacks. So I do it implicitly first, and then
        // explicitly

        // the maximum of Ndims_extra_this for all the arguments. Each one COULD
        // be <0 but Ndims_extra is capped at the bottom at 0
        int Ndims_extra = 0;

#define DECLARE_DIM_VARS(name)                                          \
        const int       PROTOTYPE_LEN_ ## name = (int)sizeof(PROTOTYPE_ ## name)/sizeof(PROTOTYPE_ ## name[0]); \
        int             __ndim__       ## name = -1;                    \
        const npy_intp* __dims__       ## name = NULL;                  \
        const npy_intp* __strides__    ## name = NULL;                  \
        npy_intp        __nbytes__     ## name = -1;                    \
        /* May be <0 */                                                 \
        int             Ndims_extra__  ## name = -1;

#define DEFINE_DIM_VARS(name)                                           \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            __ndim__    ## name = PyArray_NDIM   (__py__ ## name);      \
            __dims__    ## name = PyArray_DIMS   (__py__ ## name);      \
            __strides__ ## name = PyArray_STRIDES(__py__ ## name);      \
            __nbytes__  ## name = PyArray_NBYTES (__py__ ## name);      \
            /* May be <0 */                                             \
            Ndims_extra__ ## name = __ndim__ ## name - PROTOTYPE_LEN_ ## name; \
            if(Ndims_extra < Ndims_extra__ ## name)                     \
                Ndims_extra = Ndims_extra__ ## name;                    \
        }


        ARGUMENTS(DECLARE_DIM_VARS);
        ARGUMENTS(DEFINE_DIM_VARS);

        const int Ndims_extra_inputs_only = Ndims_extra;

        OUTPUTS(  DECLARE_DIM_VARS);
        OUTPUTS(  DEFINE_DIM_VARS);
        // Any outputs that are given are processed here. Outputs that are NOT
        // given are skipped for now. I'll create them later, and do the
        // necessary updates and checks later by expanding DEFINE_DIM_VARS later

        npy_intp dims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++)
            dims_extra[i] = 1;

        npy_intp dims_named[Ndims_named];
        for(int i=0; i<Ndims_named; i++)
            dims_named[i] = -1;

#define PARSE_DIMS(name)                                                \
        if((PyObject*)__py__ ## name != Py_None && __py__ ## name != NULL) \
        {                                                               \
            if(!parse_dim_for_one_arg(/* input and output */            \
                                      dims_named, dims_extra,           \
                                                                        \
                                      /* input */                       \
                                      Ndims_extra,                      \
                                      Ndims_extra_inputs_only,          \
                                      #name,                            \
                                      Ndims_extra__ ## name,            \
                                      PROTOTYPE_ ## name, PROTOTYPE_LEN_ ## name, \
                                      __dims__   ## name, __ndim__       ## name, \
                                      is_output))                       \
                goto done;                                              \
        }

        bool is_output;

        is_output = false;
        ARGUMENTS(PARSE_DIMS);
        is_output = true;
        OUTPUTS(  PARSE_DIMS);


        // now have dims_extra,dims_named;


#define CHECK_DIMS_NAMED_KNOWN(name)                                    \
        for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                     \
            if(PROTOTYPE_ ## name[i] < 0 &&                             \
               dims_named[-PROTOTYPE_ ## name[i]-1] < 0)                \
            {                                                           \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Output prototype " #name " dimension %d is named, but not defined by the input. You MUST pass in-place output array(s) to define these dimensions", \
                             i);                                         \
                goto done;                                              \
            }
        OUTPUTS(CHECK_DIMS_NAMED_KNOWN);
        // I don't check the inputs; parse_dim() would have barfed if any named
        // input dimension wasn't determined. The outputs don't all exist yet,
        // so I need to check


        // The dimensions of each output must be (dims_extra + PROTOTYPE__output__)
#define CREATE_MISSING_OUTPUT(name)                                    \
        if((PyObject*)__py__ ## name == Py_None || __py__ ## name == NULL) \
        {                                                               \
            int Ndims_output = Ndims_extra + PROTOTYPE_LEN_ ## name;    \
            npy_intp dims_output_want[Ndims_output];                    \
            for(int i=0; i<Ndims_extra; i++)                            \
                dims_output_want[i] = dims_extra[i];                    \
            for(int i=0; i<PROTOTYPE_LEN_ ## name; i++)                 \
                if(PROTOTYPE_ ## name[i] < 0 )                          \
                    dims_output_want[i+Ndims_extra] = dims_named[-PROTOTYPE_ ## name[i]-1]; \
                    /* I know the dims_named is defined. Checked it above */ \
                else                                                    \
                    dims_output_want[i+Ndims_extra] = PROTOTYPE_ ## name[i]; \
                                                                        \
            /* No output array available. Make one                  */  \
            __py__ ## name = (PyArrayObject*)PyArray_SimpleNew(Ndims_output, dims_output_want, selected_typenum__ ## name); \
            if(__py__ ## name == NULL)                                  \
            {                                                           \
                /* Error already set. I simply exit                 */  \
                goto done;                                              \
            }                                                           \
                                                                        \
            if(populate_output_tuple__i >= 0)                           \
            {                                                           \
                PyTuple_SET_ITEM(__py__output__arg,                     \
                                 populate_output_tuple__i,              \
                                 (PyObject*)__py__ ## name);            \
                populate_output_tuple__i++;                             \
                Py_INCREF(__py__ ## name);                              \
            }                                                           \
            else if(__py__output__arg == NULL)                          \
            {                                                           \
                /* one output, no output given */                       \
                __py__output__arg = (PyObject*)__py__ ## name;          \
                Py_INCREF(__py__output__arg);                           \
            }                                                           \
            DEFINE_DIM_VARS(name);                                      \
        }
        OUTPUTS(CREATE_MISSING_OUTPUT);


        // I'm done messing around with the dimensions. Everything passed, and
        // all the arrays have been created. Some arrays MAY have some implicit
        // length-1 dimensions. I can't communicate this to the validation and
        // slice functions. So I explicitly make copies of the dimension and
        // stride arrays, making any implicit length-1 dimensions explicit. The
        // callbacks then see all the dimension data in memory.
        //
        // Most of the time we won't have any implicit dimensions, so these
        // mounted shapes would then be copies of the normal ones
#define MAKE_MOUNTED_COPIES(name)                                       \
        int __ndim__mounted__ ## name = __ndim__ ## name;               \
        if( __ndim__ ## name < PROTOTYPE_LEN_ ## name )                 \
            /* Too few input dimensions. Add dummy dimension of length 1 */ \
            __ndim__mounted__ ## name = PROTOTYPE_LEN_ ## name;         \
        npy_intp __dims__mounted__    ## name[__ndim__mounted__ ## name]; \
        npy_intp __strides__mounted__ ## name[__ndim__mounted__ ## name]; \
        {                                                               \
            int i_dim = -1;                                             \
            for(; i_dim >= -__ndim__ ## name; i_dim--)                  \
                {                                                       \
                    /* copies of the original shapes */                 \
                    __dims__mounted__   ## name[i_dim + __ndim__mounted__ ## name] = __dims__    ## name[i_dim + __ndim__ ## name]; \
                    __strides__mounted__## name[i_dim + __ndim__mounted__ ## name] = __strides__ ## name[i_dim + __ndim__ ## name]; \
                }                                                       \
            for(; i_dim >= -__ndim__mounted__ ## name; i_dim--)         \
                {                                                       \
                    /* extra dummy dimensions, as needed */             \
                    __dims__mounted__    ## name[i_dim + __ndim__mounted__ ## name] = 1; \
                    __strides__mounted__ ## name[i_dim + __ndim__mounted__ ## name] = __nbytes__ ## name; \
                }                                                       \
        }                                                               \
        /* Now guaranteed >= 0 because of the padding */                \
        int Ndims_extra__mounted__ ## name = __ndim__mounted__ ## name - PROTOTYPE_LEN_ ## name; \
                                                                        \
        /* Ndims_extra and dims_extra[] are already right */

        ARGUMENTS(MAKE_MOUNTED_COPIES);
        OUTPUTS(  MAKE_MOUNTED_COPIES);







        // Each output variable is now an allocated array, and each one has a
        // reference. The argument __py__output__arg ALSO has a reference

#define ARGLIST_CALL_USER_CALLBACK(name)                                \
        __ndim__mounted__       ## name ,                               \
        __dims__mounted__       ## name,                                \
        __strides__mounted__    ## name,                                \
        __ndim__mounted__       ## name - Ndims_extra__mounted__ ## name, \
        &__dims__mounted__      ## name[  Ndims_extra__mounted__ ## name ], \
        &__strides__mounted__   ## name[  Ndims_extra__mounted__ ## name ], \
        PyArray_ITEMSIZE(__py__ ## name),                               \
        (void*)data_argument__  ## name,

#define DEFINE_DATA_ARGUMENT(name) char* data_argument__ ## name;
#define INIT_DATA_ARGUMENT(name) data_argument__ ## name = PyArray_DATA(__py__ ## name);

        ARGUMENTS(DEFINE_DATA_ARGUMENT);
        OUTPUTS(  DEFINE_DATA_ARGUMENT);
        ARGUMENTS(INIT_DATA_ARGUMENT);
        OUTPUTS(  INIT_DATA_ARGUMENT);

        if( ! ___stereo_range_sparse__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          rectification_model_type,cookie) )
        {
            if(PyErr_Occurred() == NULL)
                PyErr_SetString(PyExc_RuntimeError, "User-provided validation failed!");
            goto done;
        }

        // if the extra dimensions are degenerate, just return the empty array
        // we have
        for(int i=0; i<Ndims_extra; i++)
            if(dims_extra[i] == 0)
            {
                __py__result__ = (PyObject*)__py__output__arg;
                goto done;
            }

        // if no broadcasting involved, just call the function
        if(Ndims_extra == 0)
        {
            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  rectification_model_type,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___stereo_range_sparse__slice failed!");
            }
            else
                __py__result__ = (PyObject*)__py__output__arg;
            goto done;
        }

#if 0
        // most of these should be __mounted ?

        // How many elements (not bytes) to advance for each broadcasted dimension.
        // Takes into account the length-1 slieces (implicit and explicit)
        int stride_extra_elements_a[Ndims_extra];
        int stride_extra_elements_b[Ndims_extra];
        for(int idim_extra=0; idim_extra<Ndims_extra; idim_extra++)
        {
            int idim;

            idim = idim_extra + Ndims_extra_a - Ndims_extra;
            if(idim>=0 && __dims__a[idim] != 1)
                stride_extra_elements_a[idim_extra] = __strides__a[idim] / sizeof(double);
            else
                stride_extra_elements_a[idim_extra] = 0;

            idim = idim_extra + Ndims_extra_b - Ndims_extra;
            if(idim>=0 && __dims__b[idim] != 1)
                stride_extra_elements_b[idim_extra] = __strides__b[idim] / sizeof(double);
            else
                stride_extra_elements_b[idim_extra] = 0;
        }
#endif

        // I checked all the dimensions and aligned everything. I have my
        // to-broadcast dimension counts.


        // Iterate through all the broadcasting output, and gather the results
        int idims_extra[Ndims_extra];
        for(int i=0; i<Ndims_extra; i++) idims_extra[i] = 0;
        do
        {
            // This loop is awkward. I don't update the slice data pointer
            // incrementally with each slice, but advance each dimension for
            // each slice. There should be a better way
            ARGUMENTS(INIT_DATA_ARGUMENT);
            OUTPUTS(  INIT_DATA_ARGUMENT);
#undef DEFINE_DATA_ARGUMENT
#undef INIT_DATA_ARGUMENT

            for( int i_dim=-1;
                 i_dim >= -Ndims_extra;
                 i_dim--)
            {
#define ADVANCE_SLICE(name)                         \
                if(i_dim + Ndims_extra__mounted__ ## name >= 0 &&                 \
                   __dims__mounted__ ## name[i_dim + Ndims_extra__mounted__ ## name] != 1) \
                    data_argument__ ## name += idims_extra[i_dim + Ndims_extra]*__strides__ ## name[i_dim + Ndims_extra__mounted__ ## name];

                ARGUMENTS(ADVANCE_SLICE);
                OUTPUTS(  ADVANCE_SLICE);
            }

            if( ! slice_function( OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                  ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                  rectification_model_type,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___stereo_range_sparse__slice failed!");
                goto done;
            }

        } while(__pywrap___stereo_range_sparse__next(idims_extra, dims_extra, Ndims_extra));

        __py__result__ = (PyObject*)__py__output__arg;
    }
 done:

    // I free the arguments (I'm done with them) and the outputs (I'm done with
    // each individual one; the thing I'm returning has its own reference)
#define FREE_PYARRAY(name) Py_XDECREF(__py__ ## name);
    ARGUMENTS(FREE_PYARRAY);
    OUTPUTS(  FREE_PYARRAY);

    if(__py__result__ == NULL)
    {
        // An error occurred. I'm not returning an output, so release that too
        Py_XDECREF(__py__output__arg);
    }

    // If we allocated any resource into the cookie earlier, we can clean it up
    // now
    

    RESET_SIGINT();
    return __py__result__;
}

#undef ARG_DEFINE
#undef ARGLIST_DECLARE
#undef ARGLIST_CALL
#undef NAMELIST
#undef PARSECODE
#undef PARSEARG
#undef DECLARE_DIM_VARS
#undef DEFINE_DIM_VARS
#undef PARSE_DIMS
#undef SLICE_ARG
#undef INPUT_PERCENT_S
#undef INPUT_TYPEOBJ
#undef ARGLIST_CALL_USER_CALLBACK
#undef ADVANCE_SLICE
#undef FREE_PYARRAY
#undef CHECK_DIMS_NAMED_KNOWN
#undef CREATE_MISSING_OUTPUT
#undef MAKE_MOUNTED_COPIES
#undef ARGUMENTS
#undef OUTPUTS
#undef _CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS__output
#undef CHECK_CONTIGUOUS_AND_SETERROR__output
#undef _CHECK_CONTIGUOUS__disparity
#undef CHECK_CONTIGUOUS__disparity
#undef CHECK_CONTIGUOUS_AND_SETERROR__disparity
#undef _CHECK_CONTIGUOUS__qrect0
#undef CHECK_CONTIGUOUS__qrect0
#undef CHECK_CONTIGUOUS_AND_SETERROR__qrect0
#undef _CHECK_CONTIGUOUS__disparity_min
#undef CHECK_CONTIGUOUS__disparity_min
#undef CHECK_CONTIGUOUS_AND_SETERROR__disparity_min
#undef _CHECK_CONTIGUOUS__disparity_max
#undef CHECK_CONTIGUOUS__disparity_max
#undef CHECK_CONTIGUOUS_AND_SETERROR__disparity_max
#undef _CHECK_CONTIGUOUS__fxycxy_rectified
#undef CHECK_CONTIGUOUS__fxycxy_rectified
#undef CHECK_CONTIGUOUS_AND_SETERROR__fxycxy_rectified
#undef _CHECK_CONTIGUOUS__baseline
#undef CHECK_CONTIGUOUS__baseline
#undef CHECK_CONTIGUOUS_AND_SETERROR__baseline

#undef CHECK_CONTIGUOUS_ALL
#undef CHECK_CONTIGUOUS_AND_SETERROR_ALL

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_function_generic.c


///////// {{{{{{{{{ /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_module_footer_generic.c
#define PYMETHODDEF_ENTRY(name,docstring)       \
    { #name,                                    \
      (PyCFunction)__pywrap__ ## name,          \
      METH_VARARGS | METH_KEYWORDS,             \
      docstring },

static PyMethodDef methods[] =
    { FUNCTIONS(PYMETHODDEF_ENTRY)
      {}
    };

#if PY_MAJOR_VERSION == 2

PyMODINIT_FUNC initbindings_npsp(void)
{
    Py_InitModule3("bindings_npsp", methods, "Low-level routines for core mrcal operations\n\nThis is the written-in-C Python extension module that underlies the core\n(un)project routines, and several low-level operations. Most of the functions in\nthis module (those prefixed with \"_\") are not meant to be called directly, but\nhave Python wrappers that should be used instead.\n\nAll functions are exported into the mrcal module. So you can call these via\nmrcal._mrcal_npsp.fff() or mrcal.fff(). The latter is preferred.\n\n");
    import_array();
}

#else

static struct PyModuleDef module_def =
    {
     PyModuleDef_HEAD_INIT,
     "bindings_npsp", "Low-level routines for core mrcal operations\n\nThis is the written-in-C Python extension module that underlies the core\n(un)project routines, and several low-level operations. Most of the functions in\nthis module (those prefixed with \"_\") are not meant to be called directly, but\nhave Python wrappers that should be used instead.\n\nAll functions are exported into the mrcal module. So you can call these via\nmrcal._mrcal_npsp.fff() or mrcal.fff(). The latter is preferred.\n\n",
     -1,
     methods
    };

PyMODINIT_FUNC PyInit_bindings_npsp(void)
{
    PyObject* module = PyModule_Create(&module_def);
    import_array();
    return module;
}

#endif

///////// }}}}}}}}} /home/robert/projects/drcal/.venv/lib/python3.13/site-packages/pywrap-templates/pywrap_module_footer_generic.c
