// THIS IS A GENERATED FILE. DO NOT MODIFY WITH CHANGES YOU WANT TO KEEP
// Generated on 2025-09-20 20:52:30 with   scripts/poseutils-genpywrap.py


#define FUNCTIONS(_) \
  _(identity_R, "Return an identity rotation matrix\n\nSYNOPSIS\n\n    print( mrcal.identity_R() )\n    ===>\n    [[1. 0. 0.]\n     [0. 1. 0.]\n     [0. 0. 1.]]\n\nAs with all the poseutils functions, the output can be written directly into a\n(possibly-non-contiguous) array, by specifying the destination in the 'out'\nkwarg ") \
  _(identity_r, "Return an identity Rodrigues rotation\n\nSYNOPSIS\n\n    print( mrcal.identity_r() )\n    ===>\n    [0. 0. 0.]\n\nAs with all the poseutils functions, the output can be written directly into a\n(possibly-non-contiguous) array, by specifying the destination in the 'out'\nkwarg") \
  _(identity_Rt, "Return an identity Rt transformation\n\nSYNOPSIS\n\n    print( mrcal.identity_Rt() )\n    ===>\n    [[1. 0. 0.]\n     [0. 1. 0.]\n     [0. 0. 1.]\n     [0. 0. 0.]]\n\nAs with all the poseutils functions, the output can be written directly into a\n(possibly-non-contiguous) array, by specifying the destination in the 'out'\nkwarg") \
  _(identity_rt, "Return an identity rt transformation\n\nSYNOPSIS\n\n    print( mrcal.identity_rt() )\n    ===>\n    [0. 0. 0. 0. 0. 0.]\n\nAs with all the poseutils functions, the output can be written directly into a\n(possibly-non-contiguous) array, by specifying the destination in the 'out'\nkwarg") \
  _(_rotate_point_R, "Rotate a point using a rotation matrix\n\nThis is an internal function. You probably want mrcal.rotate_point_R(). See the\ndocs for that function for details.\n\n") \
  _(_rotate_point_R_withgrad, "Rotate a point using a rotation matrix; report the result and gradients\n\nThis is an internal function. You probably want mrcal.rotate_point_R(). See the\ndocs for that function for details.\n") \
  _(_rotate_point_r, "Rotate a point using a Rodrigues vector\n\nThis is an internal function. You probably want mrcal.rotate_point_r(). See the\ndocs for that function for details.\n") \
  _(_rotate_point_r_withgrad, "Rotate a point using a Rodrigues vector; report the result and gradients\n\nThis is an internal function. You probably want mrcal.rotate_point_r(). See the\ndocs for that function for details.\n") \
  _(_transform_point_Rt, "Transform a point using an Rt transformation\n\nThis is an internal function. You probably want mrcal.transform_point_Rt(). See\nthe docs for that function for details.\n") \
  _(_transform_point_Rt_withgrad, "Transform a point using an Rt transformation; report the result and gradients\n\nThis is an internal function. You probably want mrcal.transform_point_Rt(). See\nthe docs for that function for details.\n") \
  _(_transform_point_rt, "Transform a point using an rt transformation\n\nThis is an internal function. You probably want mrcal.transform_point_rt(). See\nthe docs for that function for details.\n") \
  _(_transform_point_rt_withgrad, "Transform a point using an rt transformation; report the result and gradients\n\nThis is an internal function. You probably want mrcal.transform_point_rt(). See\nthe docs for that function for details.\n") \
  _(_r_from_R, "Compute a Rodrigues vector from a rotation matrix\n\nThis is an internal function. You probably want mrcal.r_from_R(). See the docs\nfor that function for details.\n") \
  _(_r_from_R_withgrad, "Compute a Rodrigues vector from a rotation matrix\n\nThis is an internal function. You probably want mrcal.r_from_R(). See the docs\nfor that function for details.\n") \
  _(_R_from_r, "Compute a rotation matrix from a Rodrigues vector\n\nThis is an internal function. You probably want mrcal.R_from_r(). See the docs\nfor that function for details.\n") \
  _(_R_from_r_withgrad, "Compute a rotation matrix from a Rodrigues vector\n\nThis is an internal function. You probably want mrcal.R_from_r(). See the docs\nfor that function for details.\n") \
  _(_invert_R, "Invert a rotation matrix\n\nThis is an internal function. You probably want mrcal.invert_R(). See the docs\nfor that function for details.\n") \
  _(_rt_from_Rt, "Compute an rt transformation from a Rt transformation\n\nThis is an internal function. You probably want mrcal.rt_from_Rt(). See the docs\nfor that function for details.\n") \
  _(_rt_from_Rt_withgrad, "Compute an rt transformation from a Rt transformation\n\nThis is an internal function. You probably want mrcal.rt_from_Rt(). See the docs\nfor that function for details.\n") \
  _(_Rt_from_rt, "Compute an Rt transformation from a rt transformation\n\nThis is an internal function. You probably want mrcal.Rt_from_rt(). See the docs\nfor that function for details.\n") \
  _(_Rt_from_rt_withgrad, "Compute an Rt transformation from a rt transformation\n\nThis is an internal function. You probably want mrcal.Rt_from_rt(). See the docs\nfor that function for details.\n") \
  _(_invert_Rt, "Invert an Rt transformation\n\nThis is an internal function. You probably want mrcal.invert_Rt(). See the docs\nfor that function for details.\n") \
  _(_invert_rt, "Invert an rt transformation\n\nThis is an internal function. You probably want mrcal.invert_rt(). See the docs\nfor that function for details.\n") \
  _(_invert_rt_withgrad, "Invert an rt transformation\n\nThis is an internal function. You probably want mrcal.invert_rt(). See the docs\nfor that function for details.\n\nNote that the C library returns limited gradients:\n\n- It returns dtout_drin,dtout_dtin only because\n\n- drout_drin always -I\n- drout_dtin always 0\n\nTHIS function combines these into a full drtout_drtin array\n\n") \
  _(_compose_Rt, "Composes two Rt transformations\n\nThis is an internal function. You probably want mrcal.compose_Rt(). See the docs\nfor that function for details. This internal function differs from compose_Rt():\n\n- It supports exactly two arguments, while compose_Rt() can compose N\n  transformations\n") \
  _(_compose_r, "Compose two angle-axis rotations\n\nThis is an internal function. You probably want mrcal.compose_r(). See the docs\nfor that function for details. This internal function differs from compose_r():\n\n- It supports exactly two arguments, while compose_r() can compose N rotations\n\n- It never reports gradients\n") \
  _(_compose_r_withgrad, "Compose two angle-axis rotations; return (r,dr/dr0,dr/dr1)\n\nThis is an internal function. You probably want mrcal.compose_r(). See the docs\nfor that function for details. This internal function differs from compose_r():\n\n- It supports exactly two arguments, while compose_r() can compose N rotations\n\n- It always reports gradients\n\n") \
  _(compose_r_tinyr0_gradientr0, "Special-case rotation composition for the uncertainty computation\n\nSYNOPSIS\n\n    r1 = rotation_axis1 * rotation_magnitude1\n\n    dr01_dr0 = compose_r_tinyr0_gradientr0(r1)\n\n    ### Another way to get the same thing (but possibly less efficiently)\n     _,dr01_dr0,_ = compose_r(np.zeros((3,),),\n                              r1,\n                              get_gradients=True)\n\nThis is a special-case subset of compose_r(). It is the same, except:\n\n- r0 is assumed to be 0, so we don't ingest it, and we don't report the\n  composition result\n- we ONLY report the dr01/dr0 gradient\n\nThis special-case function is a part of the projection uncertainty computation,\nso it exists separate from compose_r(). See the documentation for compose_r()\nfor all the details.\n\nThis function supports broadcasting fully.\n\nARGUMENTS\n\n- r1: the second of the two rotations being composed. The first rotation is an\n  identity, so it's not given\n\n- out: optional argument specifying the destination. By default, a new numpy\n  array is created and returned. To write the results into an existing (and\n  possibly non-contiguous) array, specify it with the 'out' kwarg\n\nRETURNED VALUE\n\nWe return a single array of shape (...,3,3): dr01/dr0\n\n") \
  _(compose_r_tinyr1_gradientr1, "Special-case rotation composition for the uncertainty computation\n\nSYNOPSIS\n\n    r0 = rotation_axis0 * rotation_magnitude0\n\n    dr01_dr1 = compose_r_tinyr1_gradientr1(r0)\n\n    ### Another way to get the same thing (but possibly less efficiently)\n     _,_,dr01_dr1 = compose_r(r0,\n                              np.zeros((3,),),\n                              get_gradients=True)\n\nThis is a special-case subset of compose_r(). It is the same, except:\n\n- r1 is assumed to be 0, so we don't ingest it, and we don't report the\n  composition result\n- we ONLY report the dr01/dr1 gradient\n\nThis special-case function is a part of the projection uncertainty computation,\nso it exists separate from compose_r(). See the documentation for compose_r()\nfor all the details.\n\nThis function supports broadcasting fully.\n\nARGUMENTS\n\n- r0: the first of the two rotations being composed. The second rotation is an\n  identity, so it's not given\n\n- out: optional argument specifying the destination. By default, a new numpy\n  array is created and returned. To write the results into an existing (and\n  possibly non-contiguous) array, specify it with the 'out' kwarg\n\nRETURNED VALUE\n\nWe return a single array of shape (...,3,3): dr01/dr1\n\n") \
  _(_compose_rt, "Compose two rt transformations\n\nThis is an internal function. You probably want mrcal.compose_rt(). See the docs\nfor that function for details. This internal function differs from compose_rt():\n\n- It supports exactly two arguments, while compose_rt() can compose N\n  transformations\n\n- It never reports gradients\n") \
  _(_compose_rt_withgrad, "Compose two rt transformations; return (rt,drt/drt0,drt/drt1)\n\nThis is an internal function. You probably want mrcal.compose_rt(). See the docs\nfor that function for details. This internal function differs from compose_rt():\n\n- It supports exactly two arguments, while compose_rt() can compose N\n  transformations\n\n- It always reports gradients\n\nNote that the C library returns limited gradients:\n\n- dr/dt0 is not returned: it is always 0\n- dr/dt1 is not returned: it is always 0\n\nTHIS function combines these into the full drtout_drt0,drtout_drt1 arrays\n\n") \
  _(R_from_quat, "Convert a rotation defined as a unit quaternion rotation to a rotation matrix\n\nSYNOPSIS\n\n    s    = np.sin(rotation_magnitude/2.)\n    c    = np.cos(rotation_magnitude/2.)\n    quat = nps.glue( c, s*rotation_axis, axis = -1)\n\n    print(quat.shape)\n    ===>\n    (4,)\n\n    R = mrcal.R_from_quat(quat)\n\n    print(R.shape)\n    ===>\n    (3,3)\n\nThis is mostly for compatibility with some old stuff. mrcal doesn't use\nquaternions anywhere. Test this thoroughly before using.\n\nThis function supports broadcasting fully.\n\nARGUMENTS\n\n- quat: array of shape (4,). The unit quaternion that defines the rotation. The\n  values in the array are (u,i,j,k)\n\n- out: optional argument specifying the destination. By default, new numpy\n  array(s) are created and returned. To write the results into existing (and\n  possibly non-contiguous) arrays, specify them with the 'out' kwarg. If 'out'\n  is given, we return the 'out' that was passed in. This is the standard\n  behavior provided by numpysane_pywrap.\n\nRETURNED VALUE\n\nWe return an array of rotation matrices. Each broadcasted slice has shape (3,3)\n\n    ") \
  _(skew_symmetric, "Return the skew-symmetric matrix used in a cross product\n\nSYNOPSIS\n\n    a = np.array(( 1.,  5.,  7.))\n    b = np.array(( 3., -.1, -10.))\n\n    A = mrcal.skew_symmetric(a)\n\n    print( nps.inner(A,b) )\n    ===>\n    [-49.3  31.  -15.1]\n\n    print( np.cross(a,b) )\n    ===>\n    [-49.3  31.  -15.1]\n\nA vector cross-product a x b can be represented as a matrix multiplication A*b\nwhere A is a skew-symmetric matrix based on the vector a. This function computes\nthis matrix A from the vector a.\n\nThis function supports broadcasting fully.\n\nARGUMENTS\n\n- a: array of shape (3,)\n\n- out: optional argument specifying the destination. By default, new numpy\n  array(s) are created and returned. To write the results into existing (and\n  possibly non-contiguous) arrays, specify them with the 'out' kwarg. If 'out'\n  is given, we return the 'out' that was passed in. This is the standard\n  behavior provided by numpysane_pywrap.\n\nRETURNED VALUE\n\nWe return the matrix A in a (3,3) numpy array\n\n    ") \
  _(_align_procrustes_vectors_R01_weights, "Compute a rotation to align two sets of direction vectors or points\n\n        This is the written-in-C Python extension module. Most of the time you want to\n        use the mrcal.poseutils wrapper module instead of this module directly. Any\n        functions not prefixed with \"_\" are meant to be called directly, without the\n        wrapper.\n\n        All functions are exported into the mrcal module. So you can call these via\n        mrcal._poseutils.fff() or mrcal.fff(). The latter is preferred.\n\n            ") \
  _(_align_procrustes_points_Rt01_weights, "Compute a rotation to align two sets of direction vectors or points\n\n        This is the written-in-C Python extension module. Most of the time you want to\n        use the mrcal.poseutils wrapper module instead of this module directly. Any\n        functions not prefixed with \"_\" are meant to be called directly, without the\n        wrapper.\n\n        All functions are exported into the mrcal module. So you can call these via\n        mrcal._poseutils.fff() or mrcal.fff(). The latter is preferred.\n\n            ") \
  _(_align_procrustes_vectors_R01_noweights, "Compute a rotation to align two sets of direction vectors or points\n\n        This is the written-in-C Python extension module. Most of the time you want to\n        use the mrcal.poseutils wrapper module instead of this module directly. Any\n        functions not prefixed with \"_\" are meant to be called directly, without the\n        wrapper.\n\n        All functions are exported into the mrcal module. So you can call these via\n        mrcal._poseutils.fff() or mrcal.fff(). The latter is preferred.\n\n            ") \
  _(_align_procrustes_points_Rt01_noweights, "Compute a rotation to align two sets of direction vectors or points\n\n        This is the written-in-C Python extension module. Most of the time you want to\n        use the mrcal.poseutils wrapper module instead of this module directly. Any\n        functions not prefixed with \"_\" are meant to be called directly, without the\n        wrapper.\n\n        All functions are exported into the mrcal module. So you can call these via\n        mrcal._poseutils.fff() or mrcal.fff(). The latter is preferred.\n\n            ") \
  _(R_aligned_to_vector, "Compute a rotation to map a given vector to [0,0,1]\n\nSYNOPSIS\n\n    # I have a plane that passes through a point p, and has a normal n. I\n    # compute a transformation from the world to a coord system aligned to the\n    # plane, with p at the origin. R_plane_world p + t_plane_world = 0:\n\n    Rt_plane_world = np.zeros((4,3), dtype=float)\n    Rt_plane_world[:3,:] = mrcal.R_aligned_to_vector(n)\n    Rt_plane_world[ 3,:] = -mrcal.rotate_point_R(Rt_plane_world[:3,:],p)\n\nThis rotation is not unique: adding any rotation around v still maps v to\n[0,0,1]. An arbitrary acceptable rotation is returned.\n\nARGUMENTS\n\n- v: a numpy array of shape (3,). The vector that the computed rotation maps to\n  [0,0,1]. Does not need to be normalized. Must be non-0\n\nRETURNED VALUES\n\nThe rotation in a (3,3) array\n\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 "poseutils.h"
#include <string.h>


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

#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_ALL() CHECK_CONTIGUOUS__output()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output()

typedef struct {  } __identity_R__cookie_t;

static
bool __identity_R__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)),
  __identity_R__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#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]))

static
bool __identity_R__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)),
  __identity_R__cookie_t* cookie __attribute__((unused)))
{

    mrcal_identity_R_full( (double*)data_slice__output,
                           strides_slice__output[0],
                           strides_slice__output[1] );
    return true;

}
#undef item__output
#undef ctype__output
#define ARGUMENTS(_)

#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__identity_R__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__identity_R__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__identity_R(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
    __identity_R__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
    __identity_R__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) __identity_R__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_poseutils_npsp.identity_R",
                                     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_output[2] = {3,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,0)
#define TYPESET_MATCHES(t0, i)                   \
        else if( __pywrap__identity_R__type_matches                \
                 (                                                      \
                             t0,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = __identity_R__ ## 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:    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:    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( ! __identity_R__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, "__identity_R__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,
                                 "__identity_R__slice failed!");
                goto done;
            }

        } while(__pywrap__identity_R__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_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   identity_r

#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_ALL() CHECK_CONTIGUOUS__output()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output()

typedef struct {  } __identity_r__cookie_t;

static
bool __identity_r__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)),
  __identity_r__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]))

static
bool __identity_r__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)),
  __identity_r__cookie_t* cookie __attribute__((unused)))
{

    mrcal_identity_r_full( (double*)data_slice__output,
                           strides_slice__output[0] );
    return true;

}
#undef item__output
#undef ctype__output
#define ARGUMENTS(_)

#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__identity_r__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__identity_r__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__identity_r(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
    __identity_r__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
    __identity_r__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) __identity_r__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_poseutils_npsp.identity_r",
                                     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_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,0)
#define TYPESET_MATCHES(t0, i)                   \
        else if( __pywrap__identity_r__type_matches                \
                 (                                                      \
                             t0,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = __identity_r__ ## 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:    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:    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( ! __identity_r__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, "__identity_r__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,
                                 "__identity_r__slice failed!");
                goto done;
            }

        } while(__pywrap__identity_r__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_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   identity_Rt

#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_ALL() CHECK_CONTIGUOUS__output()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output()

typedef struct {  } __identity_Rt__cookie_t;

static
bool __identity_Rt__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)),
  __identity_Rt__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#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]))

static
bool __identity_Rt__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)),
  __identity_Rt__cookie_t* cookie __attribute__((unused)))
{

    mrcal_identity_Rt_full( (double*)data_slice__output,
                            strides_slice__output[0],
                            strides_slice__output[1] );
    return true;

}
#undef item__output
#undef ctype__output
#define ARGUMENTS(_)

#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__identity_Rt__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__identity_Rt__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__identity_Rt(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
    __identity_Rt__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
    __identity_Rt__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) __identity_Rt__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_poseutils_npsp.identity_Rt",
                                     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_output[2] = {4,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,0)
#define TYPESET_MATCHES(t0, i)                   \
        else if( __pywrap__identity_Rt__type_matches                \
                 (                                                      \
                             t0,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = __identity_Rt__ ## 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:    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:    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( ! __identity_Rt__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, "__identity_Rt__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,
                                 "__identity_Rt__slice failed!");
                goto done;
            }

        } while(__pywrap__identity_Rt__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_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   identity_rt

#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_ALL() CHECK_CONTIGUOUS__output()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output()

typedef struct {  } __identity_rt__cookie_t;

static
bool __identity_rt__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)),
  __identity_rt__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]))

static
bool __identity_rt__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)),
  __identity_rt__cookie_t* cookie __attribute__((unused)))
{

    mrcal_identity_rt_full( (double*)data_slice__output,
                            strides_slice__output[0] );
    return true;

}
#undef item__output
#undef ctype__output
#define ARGUMENTS(_)

#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__identity_rt__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__identity_rt__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__identity_rt(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
    __identity_rt__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
    __identity_rt__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) __identity_rt__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_poseutils_npsp.identity_rt",
                                     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_output[1] = {6};
    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,0)
#define TYPESET_MATCHES(t0, i)                   \
        else if( __pywrap__identity_rt__type_matches                \
                 (                                                      \
                             t0,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = __identity_rt__ ## 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:    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:    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( ! __identity_rt__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, "__identity_rt__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,
                                 "__identity_rt__slice failed!");
                goto done;
            }

        } while(__pywrap__identity_rt__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_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   _rotate_point_R

#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__R(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__R; i++)                               \
      if(dims_full__R[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__R; i--)                       \
      {                                                                 \
          if(strides_slice__R[i+Ndims_slice__R] != sizeof_element__R*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'R' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__R[i+Ndims_slice__R];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__R()              _CHECK_CONTIGUOUS__R(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__R() _CHECK_CONTIGUOUS__R(true)


#define _CHECK_CONTIGUOUS__x(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__x; i++)                               \
      if(dims_full__x[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__x; i--)                       \
      {                                                                 \
          if(strides_slice__x[i+Ndims_slice__x] != sizeof_element__x*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'x' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__x[i+Ndims_slice__x];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__x()              _CHECK_CONTIGUOUS__x(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__x() _CHECK_CONTIGUOUS__x(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__R() && CHECK_CONTIGUOUS__x()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__R() && CHECK_CONTIGUOUS_AND_SETERROR__x()

typedef struct {  } ___rotate_point_R__cookie_t;

static
bool ___rotate_point_R__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__R __attribute__((unused)),
  const npy_intp* dims_full__R __attribute__((unused)),
  const npy_intp* strides_full__R __attribute__((unused)),
  const int Ndims_slice__R __attribute__((unused)),
  const npy_intp* dims_slice__R __attribute__((unused)),
  const npy_intp* strides_slice__R __attribute__((unused)),
  npy_intp sizeof_element__R __attribute__((unused)),
  void* data__R __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___rotate_point_R__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__R npy_float64
#define item__R(__ivar0,__ivar1) (*(ctype__R*)(data_slice__R + (__ivar0)*strides_slice__R[0]+ (__ivar1)*strides_slice__R[1]))
#define ctype__x npy_float64
#define item__x(__ivar0) (*(ctype__x*)(data_slice__x + (__ivar0)*strides_slice__x[0]))

static
bool ___rotate_point_R__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__R __attribute__((unused)),
  const npy_intp* dims_full__R __attribute__((unused)),
  const npy_intp* strides_full__R __attribute__((unused)),
  const int Ndims_slice__R __attribute__((unused)),
  const npy_intp* dims_slice__R __attribute__((unused)),
  const npy_intp* strides_slice__R __attribute__((unused)),
  npy_intp sizeof_element__R __attribute__((unused)),
  void* data_slice__R __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data_slice__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___rotate_point_R__cookie_t* cookie __attribute__((unused)))
{

    mrcal_rotate_point_R_full( (double*)data_slice__output,
                               strides_slice__output[0],
                               NULL,0,0,0,
                               NULL,0,0,
                               (const double*)data_slice__R,
                               strides_slice__R[0],
                               strides_slice__R[1],
                               (const double*)data_slice__x,
                               strides_slice__x[0],
                               *inverted );
    return true;

}
#undef item__output
#undef ctype__output
#undef item__R
#undef ctype__R
#undef item__x
#undef ctype__x
#define ARGUMENTS(_) \
  _(R) \
  _(x)

#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___rotate_point_R__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___rotate_point_R__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___rotate_point_R(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
    ___rotate_point_R__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
    ___rotate_point_R__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted __attribute__((unused)),___rotate_point_R__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p" ":bindings_poseutils_npsp._rotate_point_R",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted,
                                     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_R[2] = {3,3};
    const npy_intp PROTOTYPE_x[1] = {3};
    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___rotate_point_R__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___rotate_point_R__ ## 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( ! ___rotate_point_R__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted,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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___rotate_point_R__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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___rotate_point_R__slice failed!");
                goto done;
            }

        } while(__pywrap___rotate_point_R__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__R
#undef CHECK_CONTIGUOUS__R
#undef CHECK_CONTIGUOUS_AND_SETERROR__R
#undef _CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS_AND_SETERROR__x

#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   _rotate_point_R_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__R(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__R; i++)                               \
      if(dims_full__R[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__R; i--)                       \
      {                                                                 \
          if(strides_slice__R[i+Ndims_slice__R] != sizeof_element__R*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'R' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__R[i+Ndims_slice__R];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__R()              _CHECK_CONTIGUOUS__R(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__R() _CHECK_CONTIGUOUS__R(true)


#define _CHECK_CONTIGUOUS__x(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__x; i++)                               \
      if(dims_full__x[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__x; i--)                       \
      {                                                                 \
          if(strides_slice__x[i+Ndims_slice__x] != sizeof_element__x*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'x' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__x[i+Ndims_slice__x];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__x()              _CHECK_CONTIGUOUS__x(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__x() _CHECK_CONTIGUOUS__x(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__output2() && CHECK_CONTIGUOUS__R() && CHECK_CONTIGUOUS__x()
#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__R() && CHECK_CONTIGUOUS_AND_SETERROR__x()

typedef struct {  } ___rotate_point_R_withgrad__cookie_t;

static
bool ___rotate_point_R_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__R __attribute__((unused)),
  const npy_intp* dims_full__R __attribute__((unused)),
  const npy_intp* strides_full__R __attribute__((unused)),
  const int Ndims_slice__R __attribute__((unused)),
  const npy_intp* dims_slice__R __attribute__((unused)),
  const npy_intp* strides_slice__R __attribute__((unused)),
  npy_intp sizeof_element__R __attribute__((unused)),
  void* data__R __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___rotate_point_R_withgrad__cookie_t* cookie __attribute__((unused)))
{
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,__ivar2) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]+ (__ivar2)*strides_slice__output1[2]))
#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__R npy_float64
#define item__R(__ivar0,__ivar1) (*(ctype__R*)(data_slice__R + (__ivar0)*strides_slice__R[0]+ (__ivar1)*strides_slice__R[1]))
#define ctype__x npy_float64
#define item__x(__ivar0) (*(ctype__x*)(data_slice__x + (__ivar0)*strides_slice__x[0]))

static
bool ___rotate_point_R_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__R __attribute__((unused)),
  const npy_intp* dims_full__R __attribute__((unused)),
  const npy_intp* strides_full__R __attribute__((unused)),
  const int Ndims_slice__R __attribute__((unused)),
  const npy_intp* dims_slice__R __attribute__((unused)),
  const npy_intp* strides_slice__R __attribute__((unused)),
  npy_intp sizeof_element__R __attribute__((unused)),
  void* data_slice__R __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data_slice__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___rotate_point_R_withgrad__cookie_t* cookie __attribute__((unused)))
{

    mrcal_rotate_point_R_full( (double*)data_slice__output0,
                               strides_slice__output0[0],
                               (double*)data_slice__output1,
                               strides_slice__output1[0],
                               strides_slice__output1[1],
                               strides_slice__output1[2],
                               (double*)data_slice__output2,
                               strides_slice__output2[0],
                               strides_slice__output2[1],
                               (const double*)data_slice__R,
                               strides_slice__R[0],
                               strides_slice__R[1],
                               (const double*)data_slice__x,
                               strides_slice__x[0],
                               *inverted );
    return true;

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__output2
#undef ctype__output2
#undef item__R
#undef ctype__R
#undef item__x
#undef ctype__x
#define ARGUMENTS(_) \
  _(R) \
  _(x)

#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___rotate_point_R_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___rotate_point_R_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___rotate_point_R_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
    ___rotate_point_R_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
    ___rotate_point_R_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted __attribute__((unused)),___rotate_point_R_withgrad__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p" ":bindings_poseutils_npsp._rotate_point_R_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted,
                                     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_R[2] = {3,3};
    const npy_intp PROTOTYPE_x[1] = {3};
    const npy_intp PROTOTYPE_output0[1] = {3};
    const npy_intp PROTOTYPE_output1[3] = {3,3,3};
    const npy_intp PROTOTYPE_output2[2] = {3,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(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___rotate_point_R_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 = ___rotate_point_R_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( ! ___rotate_point_R_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted,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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___rotate_point_R_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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___rotate_point_R_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___rotate_point_R_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__R
#undef CHECK_CONTIGUOUS__R
#undef CHECK_CONTIGUOUS_AND_SETERROR__R
#undef _CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS_AND_SETERROR__x

#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   _rotate_point_r

#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__r(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__r; i++)                               \
      if(dims_full__r[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__r; i--)                       \
      {                                                                 \
          if(strides_slice__r[i+Ndims_slice__r] != sizeof_element__r*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'r' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__r[i+Ndims_slice__r];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__r()              _CHECK_CONTIGUOUS__r(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__r() _CHECK_CONTIGUOUS__r(true)


#define _CHECK_CONTIGUOUS__x(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__x; i++)                               \
      if(dims_full__x[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__x; i--)                       \
      {                                                                 \
          if(strides_slice__x[i+Ndims_slice__x] != sizeof_element__x*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'x' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__x[i+Ndims_slice__x];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__x()              _CHECK_CONTIGUOUS__x(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__x() _CHECK_CONTIGUOUS__x(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__r() && CHECK_CONTIGUOUS__x()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__r() && CHECK_CONTIGUOUS_AND_SETERROR__x()

typedef struct {  } ___rotate_point_r__cookie_t;

static
bool ___rotate_point_r__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__r __attribute__((unused)),
  const npy_intp* dims_full__r __attribute__((unused)),
  const npy_intp* strides_full__r __attribute__((unused)),
  const int Ndims_slice__r __attribute__((unused)),
  const npy_intp* dims_slice__r __attribute__((unused)),
  const npy_intp* strides_slice__r __attribute__((unused)),
  npy_intp sizeof_element__r __attribute__((unused)),
  void* data__r __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___rotate_point_r__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__r npy_float64
#define item__r(__ivar0) (*(ctype__r*)(data_slice__r + (__ivar0)*strides_slice__r[0]))
#define ctype__x npy_float64
#define item__x(__ivar0) (*(ctype__x*)(data_slice__x + (__ivar0)*strides_slice__x[0]))

static
bool ___rotate_point_r__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__r __attribute__((unused)),
  const npy_intp* dims_full__r __attribute__((unused)),
  const npy_intp* strides_full__r __attribute__((unused)),
  const int Ndims_slice__r __attribute__((unused)),
  const npy_intp* dims_slice__r __attribute__((unused)),
  const npy_intp* strides_slice__r __attribute__((unused)),
  npy_intp sizeof_element__r __attribute__((unused)),
  void* data_slice__r __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data_slice__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___rotate_point_r__cookie_t* cookie __attribute__((unused)))
{

    mrcal_rotate_point_r_full( (double*)data_slice__output,
                               strides_slice__output[0],
                               NULL,0,0,
                               NULL,0,0,
                               (const double*)data_slice__r,
                               strides_slice__r[0],
                               (const double*)data_slice__x,
                               strides_slice__x[0],
                               *inverted);
    return true;

}
#undef item__output
#undef ctype__output
#undef item__r
#undef ctype__r
#undef item__x
#undef ctype__x
#define ARGUMENTS(_) \
  _(r) \
  _(x)

#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___rotate_point_r__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___rotate_point_r__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___rotate_point_r(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
    ___rotate_point_r__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
    ___rotate_point_r__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted __attribute__((unused)),___rotate_point_r__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p" ":bindings_poseutils_npsp._rotate_point_r",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted,
                                     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_r[1] = {3};
    const npy_intp PROTOTYPE_x[1] = {3};
    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___rotate_point_r__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___rotate_point_r__ ## 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( ! ___rotate_point_r__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted,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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___rotate_point_r__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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___rotate_point_r__slice failed!");
                goto done;
            }

        } while(__pywrap___rotate_point_r__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__r
#undef CHECK_CONTIGUOUS__r
#undef CHECK_CONTIGUOUS_AND_SETERROR__r
#undef _CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS_AND_SETERROR__x

#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   _rotate_point_r_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__r(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__r; i++)                               \
      if(dims_full__r[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__r; i--)                       \
      {                                                                 \
          if(strides_slice__r[i+Ndims_slice__r] != sizeof_element__r*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'r' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__r[i+Ndims_slice__r];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__r()              _CHECK_CONTIGUOUS__r(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__r() _CHECK_CONTIGUOUS__r(true)


#define _CHECK_CONTIGUOUS__x(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__x; i++)                               \
      if(dims_full__x[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__x; i--)                       \
      {                                                                 \
          if(strides_slice__x[i+Ndims_slice__x] != sizeof_element__x*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'x' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__x[i+Ndims_slice__x];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__x()              _CHECK_CONTIGUOUS__x(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__x() _CHECK_CONTIGUOUS__x(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__output2() && CHECK_CONTIGUOUS__r() && CHECK_CONTIGUOUS__x()
#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__r() && CHECK_CONTIGUOUS_AND_SETERROR__x()

typedef struct {  } ___rotate_point_r_withgrad__cookie_t;

static
bool ___rotate_point_r_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__r __attribute__((unused)),
  const npy_intp* dims_full__r __attribute__((unused)),
  const npy_intp* strides_full__r __attribute__((unused)),
  const int Ndims_slice__r __attribute__((unused)),
  const npy_intp* dims_slice__r __attribute__((unused)),
  const npy_intp* strides_slice__r __attribute__((unused)),
  npy_intp sizeof_element__r __attribute__((unused)),
  void* data__r __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___rotate_point_r_withgrad__cookie_t* cookie __attribute__((unused)))
{
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__r npy_float64
#define item__r(__ivar0) (*(ctype__r*)(data_slice__r + (__ivar0)*strides_slice__r[0]))
#define ctype__x npy_float64
#define item__x(__ivar0) (*(ctype__x*)(data_slice__x + (__ivar0)*strides_slice__x[0]))

static
bool ___rotate_point_r_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__r __attribute__((unused)),
  const npy_intp* dims_full__r __attribute__((unused)),
  const npy_intp* strides_full__r __attribute__((unused)),
  const int Ndims_slice__r __attribute__((unused)),
  const npy_intp* dims_slice__r __attribute__((unused)),
  const npy_intp* strides_slice__r __attribute__((unused)),
  npy_intp sizeof_element__r __attribute__((unused)),
  void* data_slice__r __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data_slice__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___rotate_point_r_withgrad__cookie_t* cookie __attribute__((unused)))
{

    mrcal_rotate_point_r_full( (double*)data_slice__output0,
                               strides_slice__output0[0],
                               (double*)data_slice__output1,
                               strides_slice__output1[0],
                               strides_slice__output1[1],
                               (double*)data_slice__output2,
                               strides_slice__output2[0],
                               strides_slice__output2[1],
                               (const double*)data_slice__r,
                               strides_slice__r[0],
                               (const double*)data_slice__x,
                               strides_slice__x[0],
                               *inverted);
    return true;

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__output2
#undef ctype__output2
#undef item__r
#undef ctype__r
#undef item__x
#undef ctype__x
#define ARGUMENTS(_) \
  _(r) \
  _(x)

#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___rotate_point_r_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___rotate_point_r_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___rotate_point_r_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
    ___rotate_point_r_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
    ___rotate_point_r_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted __attribute__((unused)),___rotate_point_r_withgrad__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p" ":bindings_poseutils_npsp._rotate_point_r_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted,
                                     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_r[1] = {3};
    const npy_intp PROTOTYPE_x[1] = {3};
    const npy_intp PROTOTYPE_output0[1] = {3};
    const npy_intp PROTOTYPE_output1[2] = {3,3};
    const npy_intp PROTOTYPE_output2[2] = {3,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(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___rotate_point_r_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 = ___rotate_point_r_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( ! ___rotate_point_r_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted,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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___rotate_point_r_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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___rotate_point_r_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___rotate_point_r_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__r
#undef CHECK_CONTIGUOUS__r
#undef CHECK_CONTIGUOUS_AND_SETERROR__r
#undef _CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS_AND_SETERROR__x

#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   _transform_point_Rt

#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__Rt(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__Rt; i++)                               \
      if(dims_full__Rt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Rt; i--)                       \
      {                                                                 \
          if(strides_slice__Rt[i+Ndims_slice__Rt] != sizeof_element__Rt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Rt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Rt[i+Ndims_slice__Rt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Rt()              _CHECK_CONTIGUOUS__Rt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Rt() _CHECK_CONTIGUOUS__Rt(true)


#define _CHECK_CONTIGUOUS__x(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__x; i++)                               \
      if(dims_full__x[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__x; i--)                       \
      {                                                                 \
          if(strides_slice__x[i+Ndims_slice__x] != sizeof_element__x*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'x' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__x[i+Ndims_slice__x];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__x()              _CHECK_CONTIGUOUS__x(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__x() _CHECK_CONTIGUOUS__x(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__Rt() && CHECK_CONTIGUOUS__x()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__Rt() && CHECK_CONTIGUOUS_AND_SETERROR__x()

typedef struct {  } ___transform_point_Rt__cookie_t;

static
bool ___transform_point_Rt__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__Rt __attribute__((unused)),
  const npy_intp* dims_full__Rt __attribute__((unused)),
  const npy_intp* strides_full__Rt __attribute__((unused)),
  const int Ndims_slice__Rt __attribute__((unused)),
  const npy_intp* dims_slice__Rt __attribute__((unused)),
  const npy_intp* strides_slice__Rt __attribute__((unused)),
  npy_intp sizeof_element__Rt __attribute__((unused)),
  void* data__Rt __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___transform_point_Rt__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__Rt npy_float64
#define item__Rt(__ivar0,__ivar1) (*(ctype__Rt*)(data_slice__Rt + (__ivar0)*strides_slice__Rt[0]+ (__ivar1)*strides_slice__Rt[1]))
#define ctype__x npy_float64
#define item__x(__ivar0) (*(ctype__x*)(data_slice__x + (__ivar0)*strides_slice__x[0]))

static
bool ___transform_point_Rt__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__Rt __attribute__((unused)),
  const npy_intp* dims_full__Rt __attribute__((unused)),
  const npy_intp* strides_full__Rt __attribute__((unused)),
  const int Ndims_slice__Rt __attribute__((unused)),
  const npy_intp* dims_slice__Rt __attribute__((unused)),
  const npy_intp* strides_slice__Rt __attribute__((unused)),
  npy_intp sizeof_element__Rt __attribute__((unused)),
  void* data_slice__Rt __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data_slice__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___transform_point_Rt__cookie_t* cookie __attribute__((unused)))
{

    mrcal_transform_point_Rt_full( (double*)data_slice__output,
                                   strides_slice__output[0],
                                   NULL,0,0,0,
                                   NULL,0,0,
                                   (const double*)data_slice__Rt,
                                   strides_slice__Rt[0],
                                   strides_slice__Rt[1],
                                   (const double*)data_slice__x,
                                   strides_slice__x[0],
                                   *inverted );
    return true;

}
#undef item__output
#undef ctype__output
#undef item__Rt
#undef ctype__Rt
#undef item__x
#undef ctype__x
#define ARGUMENTS(_) \
  _(Rt) \
  _(x)

#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___transform_point_Rt__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___transform_point_Rt__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___transform_point_Rt(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
    ___transform_point_Rt__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
    ___transform_point_Rt__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted __attribute__((unused)),___transform_point_Rt__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p" ":bindings_poseutils_npsp._transform_point_Rt",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted,
                                     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_Rt[2] = {4,3};
    const npy_intp PROTOTYPE_x[1] = {3};
    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___transform_point_Rt__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___transform_point_Rt__ ## 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( ! ___transform_point_Rt__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted,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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___transform_point_Rt__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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___transform_point_Rt__slice failed!");
                goto done;
            }

        } while(__pywrap___transform_point_Rt__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__Rt
#undef CHECK_CONTIGUOUS__Rt
#undef CHECK_CONTIGUOUS_AND_SETERROR__Rt
#undef _CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS_AND_SETERROR__x

#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   _transform_point_Rt_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__Rt(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__Rt; i++)                               \
      if(dims_full__Rt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Rt; i--)                       \
      {                                                                 \
          if(strides_slice__Rt[i+Ndims_slice__Rt] != sizeof_element__Rt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Rt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Rt[i+Ndims_slice__Rt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Rt()              _CHECK_CONTIGUOUS__Rt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Rt() _CHECK_CONTIGUOUS__Rt(true)


#define _CHECK_CONTIGUOUS__x(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__x; i++)                               \
      if(dims_full__x[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__x; i--)                       \
      {                                                                 \
          if(strides_slice__x[i+Ndims_slice__x] != sizeof_element__x*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'x' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__x[i+Ndims_slice__x];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__x()              _CHECK_CONTIGUOUS__x(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__x() _CHECK_CONTIGUOUS__x(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__output2() && CHECK_CONTIGUOUS__Rt() && CHECK_CONTIGUOUS__x()
#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__Rt() && CHECK_CONTIGUOUS_AND_SETERROR__x()

typedef struct {  } ___transform_point_Rt_withgrad__cookie_t;

static
bool ___transform_point_Rt_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__Rt __attribute__((unused)),
  const npy_intp* dims_full__Rt __attribute__((unused)),
  const npy_intp* strides_full__Rt __attribute__((unused)),
  const int Ndims_slice__Rt __attribute__((unused)),
  const npy_intp* dims_slice__Rt __attribute__((unused)),
  const npy_intp* strides_slice__Rt __attribute__((unused)),
  npy_intp sizeof_element__Rt __attribute__((unused)),
  void* data__Rt __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___transform_point_Rt_withgrad__cookie_t* cookie __attribute__((unused)))
{
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,__ivar2) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]+ (__ivar2)*strides_slice__output1[2]))
#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__Rt npy_float64
#define item__Rt(__ivar0,__ivar1) (*(ctype__Rt*)(data_slice__Rt + (__ivar0)*strides_slice__Rt[0]+ (__ivar1)*strides_slice__Rt[1]))
#define ctype__x npy_float64
#define item__x(__ivar0) (*(ctype__x*)(data_slice__x + (__ivar0)*strides_slice__x[0]))

static
bool ___transform_point_Rt_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__Rt __attribute__((unused)),
  const npy_intp* dims_full__Rt __attribute__((unused)),
  const npy_intp* strides_full__Rt __attribute__((unused)),
  const int Ndims_slice__Rt __attribute__((unused)),
  const npy_intp* dims_slice__Rt __attribute__((unused)),
  const npy_intp* strides_slice__Rt __attribute__((unused)),
  npy_intp sizeof_element__Rt __attribute__((unused)),
  void* data_slice__Rt __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data_slice__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___transform_point_Rt_withgrad__cookie_t* cookie __attribute__((unused)))
{

    mrcal_transform_point_Rt_full( (double*)data_slice__output0,
                                   strides_slice__output0[0],
                                   (double*)data_slice__output1,
                                   strides_slice__output1[0],
                                   strides_slice__output1[1],
                                   strides_slice__output1[2],
                                   (double*)data_slice__output2,
                                   strides_slice__output2[0],
                                   strides_slice__output2[1],
                                   (const double*)data_slice__Rt,
                                   strides_slice__Rt[0],
                                   strides_slice__Rt[1],
                                   (const double*)data_slice__x,
                                   strides_slice__x[0],
                                   *inverted );
    return true;

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__output2
#undef ctype__output2
#undef item__Rt
#undef ctype__Rt
#undef item__x
#undef ctype__x
#define ARGUMENTS(_) \
  _(Rt) \
  _(x)

#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___transform_point_Rt_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___transform_point_Rt_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___transform_point_Rt_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
    ___transform_point_Rt_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
    ___transform_point_Rt_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted __attribute__((unused)),___transform_point_Rt_withgrad__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p" ":bindings_poseutils_npsp._transform_point_Rt_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted,
                                     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_Rt[2] = {4,3};
    const npy_intp PROTOTYPE_x[1] = {3};
    const npy_intp PROTOTYPE_output0[1] = {3};
    const npy_intp PROTOTYPE_output1[3] = {3,4,3};
    const npy_intp PROTOTYPE_output2[2] = {3,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(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___transform_point_Rt_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 = ___transform_point_Rt_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( ! ___transform_point_Rt_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted,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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___transform_point_Rt_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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___transform_point_Rt_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___transform_point_Rt_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__Rt
#undef CHECK_CONTIGUOUS__Rt
#undef CHECK_CONTIGUOUS_AND_SETERROR__Rt
#undef _CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS_AND_SETERROR__x

#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   _transform_point_rt

#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__rt(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__rt; i++)                               \
      if(dims_full__rt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__rt; i--)                       \
      {                                                                 \
          if(strides_slice__rt[i+Ndims_slice__rt] != sizeof_element__rt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'rt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__rt[i+Ndims_slice__rt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__rt()              _CHECK_CONTIGUOUS__rt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__rt() _CHECK_CONTIGUOUS__rt(true)


#define _CHECK_CONTIGUOUS__x(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__x; i++)                               \
      if(dims_full__x[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__x; i--)                       \
      {                                                                 \
          if(strides_slice__x[i+Ndims_slice__x] != sizeof_element__x*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'x' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__x[i+Ndims_slice__x];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__x()              _CHECK_CONTIGUOUS__x(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__x() _CHECK_CONTIGUOUS__x(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__rt() && CHECK_CONTIGUOUS__x()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__rt() && CHECK_CONTIGUOUS_AND_SETERROR__x()

typedef struct {  } ___transform_point_rt__cookie_t;

static
bool ___transform_point_rt__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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data__rt __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___transform_point_rt__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__rt npy_float64
#define item__rt(__ivar0) (*(ctype__rt*)(data_slice__rt + (__ivar0)*strides_slice__rt[0]))
#define ctype__x npy_float64
#define item__x(__ivar0) (*(ctype__x*)(data_slice__x + (__ivar0)*strides_slice__x[0]))

static
bool ___transform_point_rt__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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data_slice__rt __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data_slice__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___transform_point_rt__cookie_t* cookie __attribute__((unused)))
{

    mrcal_transform_point_rt_full( (double*)data_slice__output,
                                   strides_slice__output[0],
                                   NULL,0,0,
                                   NULL,0,0,
                                   (const double*)data_slice__rt,
                                   strides_slice__rt[0],
                                   (const double*)data_slice__x,
                                   strides_slice__x[0],
                                   *inverted );
    return true;

}
#undef item__output
#undef ctype__output
#undef item__rt
#undef ctype__rt
#undef item__x
#undef ctype__x
#define ARGUMENTS(_) \
  _(rt) \
  _(x)

#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___transform_point_rt__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___transform_point_rt__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___transform_point_rt(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
    ___transform_point_rt__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
    ___transform_point_rt__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted __attribute__((unused)),___transform_point_rt__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p" ":bindings_poseutils_npsp._transform_point_rt",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted,
                                     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_rt[1] = {6};
    const npy_intp PROTOTYPE_x[1] = {3};
    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___transform_point_rt__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___transform_point_rt__ ## 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( ! ___transform_point_rt__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted,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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___transform_point_rt__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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___transform_point_rt__slice failed!");
                goto done;
            }

        } while(__pywrap___transform_point_rt__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__rt
#undef CHECK_CONTIGUOUS__rt
#undef CHECK_CONTIGUOUS_AND_SETERROR__rt
#undef _CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS_AND_SETERROR__x

#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   _transform_point_rt_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__rt(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__rt; i++)                               \
      if(dims_full__rt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__rt; i--)                       \
      {                                                                 \
          if(strides_slice__rt[i+Ndims_slice__rt] != sizeof_element__rt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'rt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__rt[i+Ndims_slice__rt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__rt()              _CHECK_CONTIGUOUS__rt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__rt() _CHECK_CONTIGUOUS__rt(true)


#define _CHECK_CONTIGUOUS__x(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__x; i++)                               \
      if(dims_full__x[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__x; i--)                       \
      {                                                                 \
          if(strides_slice__x[i+Ndims_slice__x] != sizeof_element__x*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'x' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__x[i+Ndims_slice__x];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__x()              _CHECK_CONTIGUOUS__x(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__x() _CHECK_CONTIGUOUS__x(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__output2() && CHECK_CONTIGUOUS__rt() && CHECK_CONTIGUOUS__x()
#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__rt() && CHECK_CONTIGUOUS_AND_SETERROR__x()

typedef struct {  } ___transform_point_rt_withgrad__cookie_t;

static
bool ___transform_point_rt_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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data__rt __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___transform_point_rt_withgrad__cookie_t* cookie __attribute__((unused)))
{
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__rt npy_float64
#define item__rt(__ivar0) (*(ctype__rt*)(data_slice__rt + (__ivar0)*strides_slice__rt[0]))
#define ctype__x npy_float64
#define item__x(__ivar0) (*(ctype__x*)(data_slice__x + (__ivar0)*strides_slice__x[0]))

static
bool ___transform_point_rt_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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data_slice__rt __attribute__((unused)),
  const int Ndims_full__x __attribute__((unused)),
  const npy_intp* dims_full__x __attribute__((unused)),
  const npy_intp* strides_full__x __attribute__((unused)),
  const int Ndims_slice__x __attribute__((unused)),
  const npy_intp* dims_slice__x __attribute__((unused)),
  const npy_intp* strides_slice__x __attribute__((unused)),
  npy_intp sizeof_element__x __attribute__((unused)),
  void* data_slice__x __attribute__((unused)),
  const int* inverted __attribute__((unused)),
  ___transform_point_rt_withgrad__cookie_t* cookie __attribute__((unused)))
{

    mrcal_transform_point_rt_full( (double*)data_slice__output0,
                                   strides_slice__output0[0],
                                   (double*)data_slice__output1,
                                   strides_slice__output1[0],
                                   strides_slice__output1[1],
                                   (double*)data_slice__output2,
                                   strides_slice__output2[0],
                                   strides_slice__output2[1],
                                   (const double*)data_slice__rt,
                                   strides_slice__rt[0],
                                   (const double*)data_slice__x,
                                   strides_slice__x[0],
                                   *inverted );
    return true;

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__output2
#undef ctype__output2
#undef item__rt
#undef ctype__rt
#undef item__x
#undef ctype__x
#define ARGUMENTS(_) \
  _(rt) \
  _(x)

#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___transform_point_rt_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___transform_point_rt_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___transform_point_rt_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
    ___transform_point_rt_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
    ___transform_point_rt_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted __attribute__((unused)),___transform_point_rt_withgrad__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p" ":bindings_poseutils_npsp._transform_point_rt_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted,
                                     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_rt[1] = {6};
    const npy_intp PROTOTYPE_x[1] = {3};
    const npy_intp PROTOTYPE_output0[1] = {3};
    const npy_intp PROTOTYPE_output1[2] = {3,6};
    const npy_intp PROTOTYPE_output2[2] = {3,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(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___transform_point_rt_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 = ___transform_point_rt_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( ! ___transform_point_rt_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted,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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___transform_point_rt_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)
                                  &inverted,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___transform_point_rt_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___transform_point_rt_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__rt
#undef CHECK_CONTIGUOUS__rt
#undef CHECK_CONTIGUOUS_AND_SETERROR__rt
#undef _CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS__x
#undef CHECK_CONTIGUOUS_AND_SETERROR__x

#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   _r_from_R

#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__R(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__R; i++)                               \
      if(dims_full__R[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__R; i--)                       \
      {                                                                 \
          if(strides_slice__R[i+Ndims_slice__R] != sizeof_element__R*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'R' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__R[i+Ndims_slice__R];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__R()              _CHECK_CONTIGUOUS__R(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__R() _CHECK_CONTIGUOUS__R(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__R()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__R()

typedef struct {  } ___r_from_R__cookie_t;

static
bool ___r_from_R__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__R __attribute__((unused)),
  const npy_intp* dims_full__R __attribute__((unused)),
  const npy_intp* strides_full__R __attribute__((unused)),
  const int Ndims_slice__R __attribute__((unused)),
  const npy_intp* dims_slice__R __attribute__((unused)),
  const npy_intp* strides_slice__R __attribute__((unused)),
  npy_intp sizeof_element__R __attribute__((unused)),
  void* data__R __attribute__((unused)),
  ___r_from_R__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__R npy_float64
#define item__R(__ivar0,__ivar1) (*(ctype__R*)(data_slice__R + (__ivar0)*strides_slice__R[0]+ (__ivar1)*strides_slice__R[1]))

static
bool ___r_from_R__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__R __attribute__((unused)),
  const npy_intp* dims_full__R __attribute__((unused)),
  const npy_intp* strides_full__R __attribute__((unused)),
  const int Ndims_slice__R __attribute__((unused)),
  const npy_intp* dims_slice__R __attribute__((unused)),
  const npy_intp* strides_slice__R __attribute__((unused)),
  npy_intp sizeof_element__R __attribute__((unused)),
  void* data_slice__R __attribute__((unused)),
  ___r_from_R__cookie_t* cookie __attribute__((unused)))
{

    mrcal_r_from_R_full(
        (double*)data_slice__output,strides_slice__output[0],
        NULL,0,0,0,
        (const double*)data_slice__R,strides_slice__R[0], strides_slice__R[1] );
    return true;

}
#undef item__output
#undef ctype__output
#undef item__R
#undef ctype__R
#define ARGUMENTS(_) \
  _(R)

#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___r_from_R__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___r_from_R__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___r_from_R(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
    ___r_from_R__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
    ___r_from_R__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___r_from_R__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_poseutils_npsp._r_from_R",
                                     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_R[2] = {3,3};
    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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap___r_from_R__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___r_from_R__ ## 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   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   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( ! ___r_from_R__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, "___r_from_R__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,
                                 "___r_from_R__slice failed!");
                goto done;
            }

        } while(__pywrap___r_from_R__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__R
#undef CHECK_CONTIGUOUS__R
#undef CHECK_CONTIGUOUS_AND_SETERROR__R

#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   _r_from_R_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__R(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__R; i++)                               \
      if(dims_full__R[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__R; i--)                       \
      {                                                                 \
          if(strides_slice__R[i+Ndims_slice__R] != sizeof_element__R*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'R' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__R[i+Ndims_slice__R];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__R()              _CHECK_CONTIGUOUS__R(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__R() _CHECK_CONTIGUOUS__R(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__R()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__R()

typedef struct {  } ___r_from_R_withgrad__cookie_t;

static
bool ___r_from_R_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__R __attribute__((unused)),
  const npy_intp* dims_full__R __attribute__((unused)),
  const npy_intp* strides_full__R __attribute__((unused)),
  const int Ndims_slice__R __attribute__((unused)),
  const npy_intp* dims_slice__R __attribute__((unused)),
  const npy_intp* strides_slice__R __attribute__((unused)),
  npy_intp sizeof_element__R __attribute__((unused)),
  void* data__R __attribute__((unused)),
  ___r_from_R_withgrad__cookie_t* cookie __attribute__((unused)))
{
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,__ivar2) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]+ (__ivar2)*strides_slice__output1[2]))
#define ctype__R npy_float64
#define item__R(__ivar0,__ivar1) (*(ctype__R*)(data_slice__R + (__ivar0)*strides_slice__R[0]+ (__ivar1)*strides_slice__R[1]))

static
bool ___r_from_R_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__R __attribute__((unused)),
  const npy_intp* dims_full__R __attribute__((unused)),
  const npy_intp* strides_full__R __attribute__((unused)),
  const int Ndims_slice__R __attribute__((unused)),
  const npy_intp* dims_slice__R __attribute__((unused)),
  const npy_intp* strides_slice__R __attribute__((unused)),
  npy_intp sizeof_element__R __attribute__((unused)),
  void* data_slice__R __attribute__((unused)),
  ___r_from_R_withgrad__cookie_t* cookie __attribute__((unused)))
{

    mrcal_r_from_R_full(
        (double*)data_slice__output0,strides_slice__output0[0],
        (double*)data_slice__output1,strides_slice__output1[0], strides_slice__output1[1],strides_slice__output1[2],
        (const double*)data_slice__R,strides_slice__R[0], strides_slice__R[1] );
    return true;

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__R
#undef ctype__R
#define ARGUMENTS(_) \
  _(R)

#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___r_from_R_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___r_from_R_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___r_from_R_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
    ___r_from_R_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
    ___r_from_R_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___r_from_R_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_poseutils_npsp._r_from_R_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_R[2] = {3,3};
    const npy_intp PROTOTYPE_output0[1] = {3};
    const npy_intp PROTOTYPE_output1[3] = {3,3,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,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___r_from_R_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___r_from_R_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   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   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( ! ___r_from_R_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, "___r_from_R_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,
                                 "___r_from_R_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___r_from_R_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__R
#undef CHECK_CONTIGUOUS__R
#undef CHECK_CONTIGUOUS_AND_SETERROR__R

#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   _R_from_r

#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__r(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__r; i++)                               \
      if(dims_full__r[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__r; i--)                       \
      {                                                                 \
          if(strides_slice__r[i+Ndims_slice__r] != sizeof_element__r*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'r' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__r[i+Ndims_slice__r];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__r()              _CHECK_CONTIGUOUS__r(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__r() _CHECK_CONTIGUOUS__r(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__r()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__r()

typedef struct {  } ___R_from_r__cookie_t;

static
bool ___R_from_r__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__r __attribute__((unused)),
  const npy_intp* dims_full__r __attribute__((unused)),
  const npy_intp* strides_full__r __attribute__((unused)),
  const int Ndims_slice__r __attribute__((unused)),
  const npy_intp* dims_slice__r __attribute__((unused)),
  const npy_intp* strides_slice__r __attribute__((unused)),
  npy_intp sizeof_element__r __attribute__((unused)),
  void* data__r __attribute__((unused)),
  ___R_from_r__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#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__r npy_float64
#define item__r(__ivar0) (*(ctype__r*)(data_slice__r + (__ivar0)*strides_slice__r[0]))

static
bool ___R_from_r__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__r __attribute__((unused)),
  const npy_intp* dims_full__r __attribute__((unused)),
  const npy_intp* strides_full__r __attribute__((unused)),
  const int Ndims_slice__r __attribute__((unused)),
  const npy_intp* dims_slice__r __attribute__((unused)),
  const npy_intp* strides_slice__r __attribute__((unused)),
  npy_intp sizeof_element__r __attribute__((unused)),
  void* data_slice__r __attribute__((unused)),
  ___R_from_r__cookie_t* cookie __attribute__((unused)))
{

    mrcal_R_from_r_full(
        (double*)data_slice__output, strides_slice__output[0], strides_slice__output[1],
        NULL,0,0,0,
        (const double*)data_slice__r, strides_slice__r[0] );
    return true;

}
#undef item__output
#undef ctype__output
#undef item__r
#undef ctype__r
#define ARGUMENTS(_) \
  _(r)

#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___R_from_r__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___R_from_r__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___R_from_r(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
    ___R_from_r__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
    ___R_from_r__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___R_from_r__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_poseutils_npsp._R_from_r",
                                     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_r[1] = {3};
    const npy_intp PROTOTYPE_output[2] = {3,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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap___R_from_r__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___R_from_r__ ## 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   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   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( ! ___R_from_r__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, "___R_from_r__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,
                                 "___R_from_r__slice failed!");
                goto done;
            }

        } while(__pywrap___R_from_r__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__r
#undef CHECK_CONTIGUOUS__r
#undef CHECK_CONTIGUOUS_AND_SETERROR__r

#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   _R_from_r_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__r(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__r; i++)                               \
      if(dims_full__r[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__r; i--)                       \
      {                                                                 \
          if(strides_slice__r[i+Ndims_slice__r] != sizeof_element__r*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'r' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__r[i+Ndims_slice__r];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__r()              _CHECK_CONTIGUOUS__r(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__r() _CHECK_CONTIGUOUS__r(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__r()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__r()

typedef struct {  } ___R_from_r_withgrad__cookie_t;

static
bool ___R_from_r_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__r __attribute__((unused)),
  const npy_intp* dims_full__r __attribute__((unused)),
  const npy_intp* strides_full__r __attribute__((unused)),
  const int Ndims_slice__r __attribute__((unused)),
  const npy_intp* dims_slice__r __attribute__((unused)),
  const npy_intp* strides_slice__r __attribute__((unused)),
  npy_intp sizeof_element__r __attribute__((unused)),
  void* data__r __attribute__((unused)),
  ___R_from_r_withgrad__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#define ctype__output0 npy_float64
#define item__output0(__ivar0,__ivar1) (*(ctype__output0*)(data_slice__output0 + (__ivar0)*strides_slice__output0[0]+ (__ivar1)*strides_slice__output0[1]))
#define ctype__output1 npy_float64
#define item__output1(__ivar0,__ivar1,__ivar2) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]+ (__ivar2)*strides_slice__output1[2]))
#define ctype__r npy_float64
#define item__r(__ivar0) (*(ctype__r*)(data_slice__r + (__ivar0)*strides_slice__r[0]))

static
bool ___R_from_r_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__r __attribute__((unused)),
  const npy_intp* dims_full__r __attribute__((unused)),
  const npy_intp* strides_full__r __attribute__((unused)),
  const int Ndims_slice__r __attribute__((unused)),
  const npy_intp* dims_slice__r __attribute__((unused)),
  const npy_intp* strides_slice__r __attribute__((unused)),
  npy_intp sizeof_element__r __attribute__((unused)),
  void* data_slice__r __attribute__((unused)),
  ___R_from_r_withgrad__cookie_t* cookie __attribute__((unused)))
{

    mrcal_R_from_r_full(
        (double*)data_slice__output0,strides_slice__output0[0], strides_slice__output0[1],
        (double*)data_slice__output1,strides_slice__output1[0], strides_slice__output1[1],strides_slice__output1[2],
        (const double*)data_slice__r, strides_slice__r[0] );
    return true;

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__r
#undef ctype__r
#define ARGUMENTS(_) \
  _(r)

#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___R_from_r_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___R_from_r_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___R_from_r_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
    ___R_from_r_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
    ___R_from_r_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___R_from_r_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_poseutils_npsp._R_from_r_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_r[1] = {3};
    const npy_intp PROTOTYPE_output0[2] = {3,3};
    const npy_intp PROTOTYPE_output1[3] = {3,3,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,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___R_from_r_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___R_from_r_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   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   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( ! ___R_from_r_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, "___R_from_r_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,
                                 "___R_from_r_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___R_from_r_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__r
#undef CHECK_CONTIGUOUS__r
#undef CHECK_CONTIGUOUS_AND_SETERROR__r

#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   _invert_R

#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__R(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__R; i++)                               \
      if(dims_full__R[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__R; i--)                       \
      {                                                                 \
          if(strides_slice__R[i+Ndims_slice__R] != sizeof_element__R*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'R' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__R[i+Ndims_slice__R];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__R()              _CHECK_CONTIGUOUS__R(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__R() _CHECK_CONTIGUOUS__R(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__R()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__R()

typedef struct {  } ___invert_R__cookie_t;

static
bool ___invert_R__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__R __attribute__((unused)),
  const npy_intp* dims_full__R __attribute__((unused)),
  const npy_intp* strides_full__R __attribute__((unused)),
  const int Ndims_slice__R __attribute__((unused)),
  const npy_intp* dims_slice__R __attribute__((unused)),
  const npy_intp* strides_slice__R __attribute__((unused)),
  npy_intp sizeof_element__R __attribute__((unused)),
  void* data__R __attribute__((unused)),
  ___invert_R__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#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__R npy_float64
#define item__R(__ivar0,__ivar1) (*(ctype__R*)(data_slice__R + (__ivar0)*strides_slice__R[0]+ (__ivar1)*strides_slice__R[1]))

static
bool ___invert_R__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__R __attribute__((unused)),
  const npy_intp* dims_full__R __attribute__((unused)),
  const npy_intp* strides_full__R __attribute__((unused)),
  const int Ndims_slice__R __attribute__((unused)),
  const npy_intp* dims_slice__R __attribute__((unused)),
  const npy_intp* strides_slice__R __attribute__((unused)),
  npy_intp sizeof_element__R __attribute__((unused)),
  void* data_slice__R __attribute__((unused)),
  ___invert_R__cookie_t* cookie __attribute__((unused)))
{

    mrcal_invert_R_full( (double*)data_slice__output,
                         strides_slice__output[0], strides_slice__output[1],
                         (const double*)data_slice__R,
                         strides_slice__R[0], strides_slice__R[1] );
    return true;

}
#undef item__output
#undef ctype__output
#undef item__R
#undef ctype__R
#define ARGUMENTS(_) \
  _(R)

#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___invert_R__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___invert_R__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___invert_R(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
    ___invert_R__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
    ___invert_R__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___invert_R__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_poseutils_npsp._invert_R",
                                     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_R[2] = {3,3};
    const npy_intp PROTOTYPE_output[2] = {3,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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap___invert_R__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___invert_R__ ## 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   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   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( ! ___invert_R__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, "___invert_R__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,
                                 "___invert_R__slice failed!");
                goto done;
            }

        } while(__pywrap___invert_R__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__R
#undef CHECK_CONTIGUOUS__R
#undef CHECK_CONTIGUOUS_AND_SETERROR__R

#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   _rt_from_Rt

#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__Rt(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__Rt; i++)                               \
      if(dims_full__Rt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Rt; i--)                       \
      {                                                                 \
          if(strides_slice__Rt[i+Ndims_slice__Rt] != sizeof_element__Rt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Rt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Rt[i+Ndims_slice__Rt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Rt()              _CHECK_CONTIGUOUS__Rt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Rt() _CHECK_CONTIGUOUS__Rt(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__Rt()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__Rt()

typedef struct {  } ___rt_from_Rt__cookie_t;

static
bool ___rt_from_Rt__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__Rt __attribute__((unused)),
  const npy_intp* dims_full__Rt __attribute__((unused)),
  const npy_intp* strides_full__Rt __attribute__((unused)),
  const int Ndims_slice__Rt __attribute__((unused)),
  const npy_intp* dims_slice__Rt __attribute__((unused)),
  const npy_intp* strides_slice__Rt __attribute__((unused)),
  npy_intp sizeof_element__Rt __attribute__((unused)),
  void* data__Rt __attribute__((unused)),
  ___rt_from_Rt__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__Rt npy_float64
#define item__Rt(__ivar0,__ivar1) (*(ctype__Rt*)(data_slice__Rt + (__ivar0)*strides_slice__Rt[0]+ (__ivar1)*strides_slice__Rt[1]))

static
bool ___rt_from_Rt__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__Rt __attribute__((unused)),
  const npy_intp* dims_full__Rt __attribute__((unused)),
  const npy_intp* strides_full__Rt __attribute__((unused)),
  const int Ndims_slice__Rt __attribute__((unused)),
  const npy_intp* dims_slice__Rt __attribute__((unused)),
  const npy_intp* strides_slice__Rt __attribute__((unused)),
  npy_intp sizeof_element__Rt __attribute__((unused)),
  void* data_slice__Rt __attribute__((unused)),
  ___rt_from_Rt__cookie_t* cookie __attribute__((unused)))
{

    mrcal_rt_from_Rt_full(
        (double*)data_slice__output,strides_slice__output[0],
        NULL,0,0,0,
        (const double*)data_slice__Rt,strides_slice__Rt[0], strides_slice__Rt[1] );
    return true;

}
#undef item__output
#undef ctype__output
#undef item__Rt
#undef ctype__Rt
#define ARGUMENTS(_) \
  _(Rt)

#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___rt_from_Rt__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___rt_from_Rt__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___rt_from_Rt(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
    ___rt_from_Rt__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
    ___rt_from_Rt__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___rt_from_Rt__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_poseutils_npsp._rt_from_Rt",
                                     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_Rt[2] = {4,3};
    const npy_intp PROTOTYPE_output[1] = {6};
    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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap___rt_from_Rt__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___rt_from_Rt__ ## 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   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   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( ! ___rt_from_Rt__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, "___rt_from_Rt__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,
                                 "___rt_from_Rt__slice failed!");
                goto done;
            }

        } while(__pywrap___rt_from_Rt__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__Rt
#undef CHECK_CONTIGUOUS__Rt
#undef CHECK_CONTIGUOUS_AND_SETERROR__Rt

#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   _rt_from_Rt_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__Rt(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__Rt; i++)                               \
      if(dims_full__Rt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Rt; i--)                       \
      {                                                                 \
          if(strides_slice__Rt[i+Ndims_slice__Rt] != sizeof_element__Rt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Rt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Rt[i+Ndims_slice__Rt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Rt()              _CHECK_CONTIGUOUS__Rt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Rt() _CHECK_CONTIGUOUS__Rt(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__Rt()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__Rt()

typedef struct {  } ___rt_from_Rt_withgrad__cookie_t;

static
bool ___rt_from_Rt_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__Rt __attribute__((unused)),
  const npy_intp* dims_full__Rt __attribute__((unused)),
  const npy_intp* strides_full__Rt __attribute__((unused)),
  const int Ndims_slice__Rt __attribute__((unused)),
  const npy_intp* dims_slice__Rt __attribute__((unused)),
  const npy_intp* strides_slice__Rt __attribute__((unused)),
  npy_intp sizeof_element__Rt __attribute__((unused)),
  void* data__Rt __attribute__((unused)),
  ___rt_from_Rt_withgrad__cookie_t* cookie __attribute__((unused)))
{
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,__ivar2) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]+ (__ivar2)*strides_slice__output1[2]))
#define ctype__Rt npy_float64
#define item__Rt(__ivar0,__ivar1) (*(ctype__Rt*)(data_slice__Rt + (__ivar0)*strides_slice__Rt[0]+ (__ivar1)*strides_slice__Rt[1]))

static
bool ___rt_from_Rt_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__Rt __attribute__((unused)),
  const npy_intp* dims_full__Rt __attribute__((unused)),
  const npy_intp* strides_full__Rt __attribute__((unused)),
  const int Ndims_slice__Rt __attribute__((unused)),
  const npy_intp* dims_slice__Rt __attribute__((unused)),
  const npy_intp* strides_slice__Rt __attribute__((unused)),
  npy_intp sizeof_element__Rt __attribute__((unused)),
  void* data_slice__Rt __attribute__((unused)),
  ___rt_from_Rt_withgrad__cookie_t* cookie __attribute__((unused)))
{

    mrcal_rt_from_Rt_full(
        (double*)data_slice__output0,strides_slice__output0[0],
        (double*)data_slice__output1,strides_slice__output1[0], strides_slice__output1[1],strides_slice__output1[2],
        (const double*)data_slice__Rt,strides_slice__Rt[0], strides_slice__Rt[1] );
    return true;

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__Rt
#undef ctype__Rt
#define ARGUMENTS(_) \
  _(Rt)

#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___rt_from_Rt_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___rt_from_Rt_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___rt_from_Rt_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
    ___rt_from_Rt_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
    ___rt_from_Rt_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___rt_from_Rt_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_poseutils_npsp._rt_from_Rt_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_Rt[2] = {4,3};
    const npy_intp PROTOTYPE_output0[1] = {6};
    const npy_intp PROTOTYPE_output1[3] = {3,3,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,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___rt_from_Rt_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___rt_from_Rt_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   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   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( ! ___rt_from_Rt_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, "___rt_from_Rt_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,
                                 "___rt_from_Rt_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___rt_from_Rt_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__Rt
#undef CHECK_CONTIGUOUS__Rt
#undef CHECK_CONTIGUOUS_AND_SETERROR__Rt

#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   _Rt_from_rt

#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__rt(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__rt; i++)                               \
      if(dims_full__rt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__rt; i--)                       \
      {                                                                 \
          if(strides_slice__rt[i+Ndims_slice__rt] != sizeof_element__rt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'rt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__rt[i+Ndims_slice__rt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__rt()              _CHECK_CONTIGUOUS__rt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__rt() _CHECK_CONTIGUOUS__rt(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__rt()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__rt()

typedef struct {  } ___Rt_from_rt__cookie_t;

static
bool ___Rt_from_rt__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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data__rt __attribute__((unused)),
  ___Rt_from_rt__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#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__rt npy_float64
#define item__rt(__ivar0) (*(ctype__rt*)(data_slice__rt + (__ivar0)*strides_slice__rt[0]))

static
bool ___Rt_from_rt__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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data_slice__rt __attribute__((unused)),
  ___Rt_from_rt__cookie_t* cookie __attribute__((unused)))
{

    mrcal_Rt_from_rt_full(
        (double*)data_slice__output, strides_slice__output[0],strides_slice__output[1],
        NULL,0,0,0,
        (const double*)data_slice__rt, strides_slice__rt[0] );
    return true;

}
#undef item__output
#undef ctype__output
#undef item__rt
#undef ctype__rt
#define ARGUMENTS(_) \
  _(rt)

#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___Rt_from_rt__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___Rt_from_rt__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___Rt_from_rt(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
    ___Rt_from_rt__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
    ___Rt_from_rt__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___Rt_from_rt__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_poseutils_npsp._Rt_from_rt",
                                     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_rt[1] = {6};
    const npy_intp PROTOTYPE_output[2] = {4,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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap___Rt_from_rt__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___Rt_from_rt__ ## 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   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   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( ! ___Rt_from_rt__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, "___Rt_from_rt__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,
                                 "___Rt_from_rt__slice failed!");
                goto done;
            }

        } while(__pywrap___Rt_from_rt__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__rt
#undef CHECK_CONTIGUOUS__rt
#undef CHECK_CONTIGUOUS_AND_SETERROR__rt

#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   _Rt_from_rt_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__rt(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__rt; i++)                               \
      if(dims_full__rt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__rt; i--)                       \
      {                                                                 \
          if(strides_slice__rt[i+Ndims_slice__rt] != sizeof_element__rt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'rt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__rt[i+Ndims_slice__rt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__rt()              _CHECK_CONTIGUOUS__rt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__rt() _CHECK_CONTIGUOUS__rt(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__rt()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__rt()

typedef struct {  } ___Rt_from_rt_withgrad__cookie_t;

static
bool ___Rt_from_rt_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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data__rt __attribute__((unused)),
  ___Rt_from_rt_withgrad__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#define ctype__output0 npy_float64
#define item__output0(__ivar0,__ivar1) (*(ctype__output0*)(data_slice__output0 + (__ivar0)*strides_slice__output0[0]+ (__ivar1)*strides_slice__output0[1]))
#define ctype__output1 npy_float64
#define item__output1(__ivar0,__ivar1,__ivar2) (*(ctype__output1*)(data_slice__output1 + (__ivar0)*strides_slice__output1[0]+ (__ivar1)*strides_slice__output1[1]+ (__ivar2)*strides_slice__output1[2]))
#define ctype__rt npy_float64
#define item__rt(__ivar0) (*(ctype__rt*)(data_slice__rt + (__ivar0)*strides_slice__rt[0]))

static
bool ___Rt_from_rt_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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data_slice__rt __attribute__((unused)),
  ___Rt_from_rt_withgrad__cookie_t* cookie __attribute__((unused)))
{

    mrcal_Rt_from_rt_full(
        (double*)data_slice__output0, strides_slice__output0[0],strides_slice__output0[1],
        (double*)data_slice__output1,strides_slice__output1[0], strides_slice__output1[1],strides_slice__output1[2],
        (const double*)data_slice__rt, strides_slice__rt[0] );
    return true;

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__rt
#undef ctype__rt
#define ARGUMENTS(_) \
  _(rt)

#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___Rt_from_rt_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___Rt_from_rt_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___Rt_from_rt_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
    ___Rt_from_rt_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
    ___Rt_from_rt_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___Rt_from_rt_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_poseutils_npsp._Rt_from_rt_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_rt[1] = {6};
    const npy_intp PROTOTYPE_output0[2] = {4,3};
    const npy_intp PROTOTYPE_output1[3] = {3,3,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,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___Rt_from_rt_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___Rt_from_rt_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   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   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( ! ___Rt_from_rt_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, "___Rt_from_rt_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,
                                 "___Rt_from_rt_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___Rt_from_rt_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__rt
#undef CHECK_CONTIGUOUS__rt
#undef CHECK_CONTIGUOUS_AND_SETERROR__rt

#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   _invert_Rt

#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__Rt(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__Rt; i++)                               \
      if(dims_full__Rt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Rt; i--)                       \
      {                                                                 \
          if(strides_slice__Rt[i+Ndims_slice__Rt] != sizeof_element__Rt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Rt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Rt[i+Ndims_slice__Rt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Rt()              _CHECK_CONTIGUOUS__Rt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Rt() _CHECK_CONTIGUOUS__Rt(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__Rt()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__Rt()

typedef struct {  } ___invert_Rt__cookie_t;

static
bool ___invert_Rt__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__Rt __attribute__((unused)),
  const npy_intp* dims_full__Rt __attribute__((unused)),
  const npy_intp* strides_full__Rt __attribute__((unused)),
  const int Ndims_slice__Rt __attribute__((unused)),
  const npy_intp* dims_slice__Rt __attribute__((unused)),
  const npy_intp* strides_slice__Rt __attribute__((unused)),
  npy_intp sizeof_element__Rt __attribute__((unused)),
  void* data__Rt __attribute__((unused)),
  ___invert_Rt__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#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__Rt npy_float64
#define item__Rt(__ivar0,__ivar1) (*(ctype__Rt*)(data_slice__Rt + (__ivar0)*strides_slice__Rt[0]+ (__ivar1)*strides_slice__Rt[1]))

static
bool ___invert_Rt__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__Rt __attribute__((unused)),
  const npy_intp* dims_full__Rt __attribute__((unused)),
  const npy_intp* strides_full__Rt __attribute__((unused)),
  const int Ndims_slice__Rt __attribute__((unused)),
  const npy_intp* dims_slice__Rt __attribute__((unused)),
  const npy_intp* strides_slice__Rt __attribute__((unused)),
  npy_intp sizeof_element__Rt __attribute__((unused)),
  void* data_slice__Rt __attribute__((unused)),
  ___invert_Rt__cookie_t* cookie __attribute__((unused)))
{

    mrcal_invert_Rt_full( (double*)data_slice__output,
                          strides_slice__output[0], strides_slice__output[1],
                          (const double*)data_slice__Rt,
                          strides_slice__Rt[0], strides_slice__Rt[1] );
    return true;

}
#undef item__output
#undef ctype__output
#undef item__Rt
#undef ctype__Rt
#define ARGUMENTS(_) \
  _(Rt)

#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___invert_Rt__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___invert_Rt__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___invert_Rt(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
    ___invert_Rt__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
    ___invert_Rt__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___invert_Rt__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_poseutils_npsp._invert_Rt",
                                     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_Rt[2] = {4,3};
    const npy_intp PROTOTYPE_output[2] = {4,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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap___invert_Rt__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___invert_Rt__ ## 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   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   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( ! ___invert_Rt__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, "___invert_Rt__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,
                                 "___invert_Rt__slice failed!");
                goto done;
            }

        } while(__pywrap___invert_Rt__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__Rt
#undef CHECK_CONTIGUOUS__Rt
#undef CHECK_CONTIGUOUS_AND_SETERROR__Rt

#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   _invert_rt

#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__rt(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__rt; i++)                               \
      if(dims_full__rt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__rt; i--)                       \
      {                                                                 \
          if(strides_slice__rt[i+Ndims_slice__rt] != sizeof_element__rt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'rt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__rt[i+Ndims_slice__rt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__rt()              _CHECK_CONTIGUOUS__rt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__rt() _CHECK_CONTIGUOUS__rt(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__rt()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__rt()

typedef struct {  } ___invert_rt__cookie_t;

static
bool ___invert_rt__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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data__rt __attribute__((unused)),
  ___invert_rt__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__rt npy_float64
#define item__rt(__ivar0) (*(ctype__rt*)(data_slice__rt + (__ivar0)*strides_slice__rt[0]))

static
bool ___invert_rt__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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data_slice__rt __attribute__((unused)),
  ___invert_rt__cookie_t* cookie __attribute__((unused)))
{

    mrcal_invert_rt_full( (double*)data_slice__output,
                          strides_slice__output[0],
                          NULL,0,0,
                          NULL,0,0,
                          (const double*)data_slice__rt,
                          strides_slice__rt[0] );
    return true;

}
#undef item__output
#undef ctype__output
#undef item__rt
#undef ctype__rt
#define ARGUMENTS(_) \
  _(rt)

#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___invert_rt__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___invert_rt__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___invert_rt(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
    ___invert_rt__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
    ___invert_rt__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___invert_rt__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_poseutils_npsp._invert_rt",
                                     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_rt[1] = {6};
    const npy_intp PROTOTYPE_output[1] = {6};
    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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap___invert_rt__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___invert_rt__ ## 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   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   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( ! ___invert_rt__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, "___invert_rt__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,
                                 "___invert_rt__slice failed!");
                goto done;
            }

        } while(__pywrap___invert_rt__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__rt
#undef CHECK_CONTIGUOUS__rt
#undef CHECK_CONTIGUOUS_AND_SETERROR__rt

#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   _invert_rt_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__rt(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__rt; i++)                               \
      if(dims_full__rt[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__rt; i--)                       \
      {                                                                 \
          if(strides_slice__rt[i+Ndims_slice__rt] != sizeof_element__rt*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'rt' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__rt[i+Ndims_slice__rt];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__rt()              _CHECK_CONTIGUOUS__rt(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__rt() _CHECK_CONTIGUOUS__rt(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__rt()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output0() && CHECK_CONTIGUOUS_AND_SETERROR__output1() && CHECK_CONTIGUOUS_AND_SETERROR__rt()

typedef struct {  } ___invert_rt_withgrad__cookie_t;

static
bool ___invert_rt_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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data__rt __attribute__((unused)),
  ___invert_rt_withgrad__cookie_t* cookie __attribute__((unused)))
{
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__rt npy_float64
#define item__rt(__ivar0) (*(ctype__rt*)(data_slice__rt + (__ivar0)*strides_slice__rt[0]))

static
bool ___invert_rt_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__rt __attribute__((unused)),
  const npy_intp* dims_full__rt __attribute__((unused)),
  const npy_intp* strides_full__rt __attribute__((unused)),
  const int Ndims_slice__rt __attribute__((unused)),
  const npy_intp* dims_slice__rt __attribute__((unused)),
  const npy_intp* strides_slice__rt __attribute__((unused)),
  npy_intp sizeof_element__rt __attribute__((unused)),
  void* data_slice__rt __attribute__((unused)),
  ___invert_rt_withgrad__cookie_t* cookie __attribute__((unused)))
{

    mrcal_invert_rt_full( (double*)data_slice__output0,
                          strides_slice__output0[0],

                          &item__output1(3,0),
                          strides_slice__output1[0], strides_slice__output1[1],

                          &item__output1(3,3),
                          strides_slice__output1[0], strides_slice__output1[1],

                          (const double*)data_slice__rt,
                          strides_slice__rt[0] );
    for(int i=0; i<3; i++)
        for(int j=0; j<6; j++)
            item__output1(i,j) = 0;
    item__output1(0,0) = -1.;
    item__output1(1,1) = -1.;
    item__output1(2,2) = -1.;

    return true;

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__rt
#undef ctype__rt
#define ARGUMENTS(_) \
  _(rt)

#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___invert_rt_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___invert_rt_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___invert_rt_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
    ___invert_rt_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
    ___invert_rt_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___invert_rt_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_poseutils_npsp._invert_rt_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_rt[1] = {6};
    const npy_intp PROTOTYPE_output0[1] = {6};
    const npy_intp PROTOTYPE_output1[2] = {6,6};
    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,0)
#define TYPESET_MATCHES(t0,t1,t2, i)                   \
        else if( __pywrap___invert_rt_withgrad__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___invert_rt_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   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   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( ! ___invert_rt_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, "___invert_rt_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,
                                 "___invert_rt_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___invert_rt_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__rt
#undef CHECK_CONTIGUOUS__rt
#undef CHECK_CONTIGUOUS_AND_SETERROR__rt

#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   _compose_Rt

#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__Rt0(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__Rt0; i++)                               \
      if(dims_full__Rt0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Rt0; i--)                       \
      {                                                                 \
          if(strides_slice__Rt0[i+Ndims_slice__Rt0] != sizeof_element__Rt0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Rt0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Rt0[i+Ndims_slice__Rt0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Rt0()              _CHECK_CONTIGUOUS__Rt0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Rt0() _CHECK_CONTIGUOUS__Rt0(true)


#define _CHECK_CONTIGUOUS__Rt1(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__Rt1; i++)                               \
      if(dims_full__Rt1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__Rt1; i--)                       \
      {                                                                 \
          if(strides_slice__Rt1[i+Ndims_slice__Rt1] != sizeof_element__Rt1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'Rt1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__Rt1[i+Ndims_slice__Rt1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__Rt1()              _CHECK_CONTIGUOUS__Rt1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__Rt1() _CHECK_CONTIGUOUS__Rt1(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__Rt0() && CHECK_CONTIGUOUS__Rt1()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__Rt0() && CHECK_CONTIGUOUS_AND_SETERROR__Rt1()

typedef struct {  } ___compose_Rt__cookie_t;

static
bool ___compose_Rt__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__Rt0 __attribute__((unused)),
  const npy_intp* dims_full__Rt0 __attribute__((unused)),
  const npy_intp* strides_full__Rt0 __attribute__((unused)),
  const int Ndims_slice__Rt0 __attribute__((unused)),
  const npy_intp* dims_slice__Rt0 __attribute__((unused)),
  const npy_intp* strides_slice__Rt0 __attribute__((unused)),
  npy_intp sizeof_element__Rt0 __attribute__((unused)),
  void* data__Rt0 __attribute__((unused)),
  const int Ndims_full__Rt1 __attribute__((unused)),
  const npy_intp* dims_full__Rt1 __attribute__((unused)),
  const npy_intp* strides_full__Rt1 __attribute__((unused)),
  const int Ndims_slice__Rt1 __attribute__((unused)),
  const npy_intp* dims_slice__Rt1 __attribute__((unused)),
  const npy_intp* strides_slice__Rt1 __attribute__((unused)),
  npy_intp sizeof_element__Rt1 __attribute__((unused)),
  void* data__Rt1 __attribute__((unused)),
  const int* inverted0 __attribute__((unused)),
  const int* inverted1 __attribute__((unused)),
  ___compose_Rt__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#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__Rt0 npy_float64
#define item__Rt0(__ivar0,__ivar1) (*(ctype__Rt0*)(data_slice__Rt0 + (__ivar0)*strides_slice__Rt0[0]+ (__ivar1)*strides_slice__Rt0[1]))
#define ctype__Rt1 npy_float64
#define item__Rt1(__ivar0,__ivar1) (*(ctype__Rt1*)(data_slice__Rt1 + (__ivar0)*strides_slice__Rt1[0]+ (__ivar1)*strides_slice__Rt1[1]))

static
bool ___compose_Rt__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__Rt0 __attribute__((unused)),
  const npy_intp* dims_full__Rt0 __attribute__((unused)),
  const npy_intp* strides_full__Rt0 __attribute__((unused)),
  const int Ndims_slice__Rt0 __attribute__((unused)),
  const npy_intp* dims_slice__Rt0 __attribute__((unused)),
  const npy_intp* strides_slice__Rt0 __attribute__((unused)),
  npy_intp sizeof_element__Rt0 __attribute__((unused)),
  void* data_slice__Rt0 __attribute__((unused)),
  const int Ndims_full__Rt1 __attribute__((unused)),
  const npy_intp* dims_full__Rt1 __attribute__((unused)),
  const npy_intp* strides_full__Rt1 __attribute__((unused)),
  const int Ndims_slice__Rt1 __attribute__((unused)),
  const npy_intp* dims_slice__Rt1 __attribute__((unused)),
  const npy_intp* strides_slice__Rt1 __attribute__((unused)),
  npy_intp sizeof_element__Rt1 __attribute__((unused)),
  void* data_slice__Rt1 __attribute__((unused)),
  const int* inverted0 __attribute__((unused)),
  const int* inverted1 __attribute__((unused)),
  ___compose_Rt__cookie_t* cookie __attribute__((unused)))
{

    mrcal_compose_Rt_full( (double*)data_slice__output,
                           strides_slice__output[0], strides_slice__output[1],
                           (const double*)data_slice__Rt0,
                           strides_slice__Rt0[0], strides_slice__Rt0[1],
                           (const double*)data_slice__Rt1,
                           strides_slice__Rt1[0], strides_slice__Rt1[1],
                           *inverted0, *inverted1);
    return true;

}
#undef item__output
#undef ctype__output
#undef item__Rt0
#undef ctype__Rt0
#undef item__Rt1
#undef ctype__Rt1
#define ARGUMENTS(_) \
  _(Rt0) \
  _(Rt1)

#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___compose_Rt__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___compose_Rt__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___compose_Rt(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
    ___compose_Rt__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
    ___compose_Rt__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted0 __attribute__((unused)),const int* inverted1 __attribute__((unused)),___compose_Rt__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted0 = false;
int inverted1 = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted0","inverted1",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p""p" ":bindings_poseutils_npsp._compose_Rt",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted0,&inverted1,
                                     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_Rt0[2] = {4,3};
    const npy_intp PROTOTYPE_Rt1[2] = {4,3};
    const npy_intp PROTOTYPE_output[2] = {4,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___compose_Rt__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___compose_Rt__ ## 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( ! ___compose_Rt__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted0,&inverted1,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)
                                  &inverted0,&inverted1,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___compose_Rt__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)
                                  &inverted0,&inverted1,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___compose_Rt__slice failed!");
                goto done;
            }

        } while(__pywrap___compose_Rt__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__Rt0
#undef CHECK_CONTIGUOUS__Rt0
#undef CHECK_CONTIGUOUS_AND_SETERROR__Rt0
#undef _CHECK_CONTIGUOUS__Rt1
#undef CHECK_CONTIGUOUS__Rt1
#undef CHECK_CONTIGUOUS_AND_SETERROR__Rt1

#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   _compose_r

#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__r0(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__r0; i++)                               \
      if(dims_full__r0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__r0; i--)                       \
      {                                                                 \
          if(strides_slice__r0[i+Ndims_slice__r0] != sizeof_element__r0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'r0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__r0[i+Ndims_slice__r0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__r0()              _CHECK_CONTIGUOUS__r0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__r0() _CHECK_CONTIGUOUS__r0(true)


#define _CHECK_CONTIGUOUS__r1(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__r1; i++)                               \
      if(dims_full__r1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__r1; i--)                       \
      {                                                                 \
          if(strides_slice__r1[i+Ndims_slice__r1] != sizeof_element__r1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'r1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__r1[i+Ndims_slice__r1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__r1()              _CHECK_CONTIGUOUS__r1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__r1() _CHECK_CONTIGUOUS__r1(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__r0() && CHECK_CONTIGUOUS__r1()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__r0() && CHECK_CONTIGUOUS_AND_SETERROR__r1()

typedef struct {  } ___compose_r__cookie_t;

static
bool ___compose_r__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__r0 __attribute__((unused)),
  const npy_intp* dims_full__r0 __attribute__((unused)),
  const npy_intp* strides_full__r0 __attribute__((unused)),
  const int Ndims_slice__r0 __attribute__((unused)),
  const npy_intp* dims_slice__r0 __attribute__((unused)),
  const npy_intp* strides_slice__r0 __attribute__((unused)),
  npy_intp sizeof_element__r0 __attribute__((unused)),
  void* data__r0 __attribute__((unused)),
  const int Ndims_full__r1 __attribute__((unused)),
  const npy_intp* dims_full__r1 __attribute__((unused)),
  const npy_intp* strides_full__r1 __attribute__((unused)),
  const int Ndims_slice__r1 __attribute__((unused)),
  const npy_intp* dims_slice__r1 __attribute__((unused)),
  const npy_intp* strides_slice__r1 __attribute__((unused)),
  npy_intp sizeof_element__r1 __attribute__((unused)),
  void* data__r1 __attribute__((unused)),
  const int* inverted0 __attribute__((unused)),
  const int* inverted1 __attribute__((unused)),
  ___compose_r__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__r0 npy_float64
#define item__r0(__ivar0) (*(ctype__r0*)(data_slice__r0 + (__ivar0)*strides_slice__r0[0]))
#define ctype__r1 npy_float64
#define item__r1(__ivar0) (*(ctype__r1*)(data_slice__r1 + (__ivar0)*strides_slice__r1[0]))

static
bool ___compose_r__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__r0 __attribute__((unused)),
  const npy_intp* dims_full__r0 __attribute__((unused)),
  const npy_intp* strides_full__r0 __attribute__((unused)),
  const int Ndims_slice__r0 __attribute__((unused)),
  const npy_intp* dims_slice__r0 __attribute__((unused)),
  const npy_intp* strides_slice__r0 __attribute__((unused)),
  npy_intp sizeof_element__r0 __attribute__((unused)),
  void* data_slice__r0 __attribute__((unused)),
  const int Ndims_full__r1 __attribute__((unused)),
  const npy_intp* dims_full__r1 __attribute__((unused)),
  const npy_intp* strides_full__r1 __attribute__((unused)),
  const int Ndims_slice__r1 __attribute__((unused)),
  const npy_intp* dims_slice__r1 __attribute__((unused)),
  const npy_intp* strides_slice__r1 __attribute__((unused)),
  npy_intp sizeof_element__r1 __attribute__((unused)),
  void* data_slice__r1 __attribute__((unused)),
  const int* inverted0 __attribute__((unused)),
  const int* inverted1 __attribute__((unused)),
  ___compose_r__cookie_t* cookie __attribute__((unused)))
{

    mrcal_compose_r_full( (double*)data_slice__output,
                           strides_slice__output[0],
                           NULL,0,0,
                           NULL,0,0,
                           (const double*)data_slice__r0,
                           strides_slice__r0[0],
                           (const double*)data_slice__r1,
                           strides_slice__r1[0],
                           *inverted0, *inverted1);
    return true;

}
#undef item__output
#undef ctype__output
#undef item__r0
#undef ctype__r0
#undef item__r1
#undef ctype__r1
#define ARGUMENTS(_) \
  _(r0) \
  _(r1)

#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___compose_r__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___compose_r__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___compose_r(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
    ___compose_r__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
    ___compose_r__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted0 __attribute__((unused)),const int* inverted1 __attribute__((unused)),___compose_r__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted0 = false;
int inverted1 = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted0","inverted1",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p""p" ":bindings_poseutils_npsp._compose_r",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted0,&inverted1,
                                     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_r0[1] = {3};
    const npy_intp PROTOTYPE_r1[1] = {3};
    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___compose_r__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___compose_r__ ## 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( ! ___compose_r__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted0,&inverted1,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)
                                  &inverted0,&inverted1,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___compose_r__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)
                                  &inverted0,&inverted1,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___compose_r__slice failed!");
                goto done;
            }

        } while(__pywrap___compose_r__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__r0
#undef CHECK_CONTIGUOUS__r0
#undef CHECK_CONTIGUOUS_AND_SETERROR__r0
#undef _CHECK_CONTIGUOUS__r1
#undef CHECK_CONTIGUOUS__r1
#undef CHECK_CONTIGUOUS_AND_SETERROR__r1

#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   _compose_r_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__r0(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__r0; i++)                               \
      if(dims_full__r0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__r0; i--)                       \
      {                                                                 \
          if(strides_slice__r0[i+Ndims_slice__r0] != sizeof_element__r0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'r0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__r0[i+Ndims_slice__r0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__r0()              _CHECK_CONTIGUOUS__r0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__r0() _CHECK_CONTIGUOUS__r0(true)


#define _CHECK_CONTIGUOUS__r1(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__r1; i++)                               \
      if(dims_full__r1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__r1; i--)                       \
      {                                                                 \
          if(strides_slice__r1[i+Ndims_slice__r1] != sizeof_element__r1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'r1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__r1[i+Ndims_slice__r1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__r1()              _CHECK_CONTIGUOUS__r1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__r1() _CHECK_CONTIGUOUS__r1(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__output2() && CHECK_CONTIGUOUS__r0() && CHECK_CONTIGUOUS__r1()
#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__r0() && CHECK_CONTIGUOUS_AND_SETERROR__r1()

typedef struct {  } ___compose_r_withgrad__cookie_t;

static
bool ___compose_r_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__r0 __attribute__((unused)),
  const npy_intp* dims_full__r0 __attribute__((unused)),
  const npy_intp* strides_full__r0 __attribute__((unused)),
  const int Ndims_slice__r0 __attribute__((unused)),
  const npy_intp* dims_slice__r0 __attribute__((unused)),
  const npy_intp* strides_slice__r0 __attribute__((unused)),
  npy_intp sizeof_element__r0 __attribute__((unused)),
  void* data__r0 __attribute__((unused)),
  const int Ndims_full__r1 __attribute__((unused)),
  const npy_intp* dims_full__r1 __attribute__((unused)),
  const npy_intp* strides_full__r1 __attribute__((unused)),
  const int Ndims_slice__r1 __attribute__((unused)),
  const npy_intp* dims_slice__r1 __attribute__((unused)),
  const npy_intp* strides_slice__r1 __attribute__((unused)),
  npy_intp sizeof_element__r1 __attribute__((unused)),
  void* data__r1 __attribute__((unused)),
  const int* inverted0 __attribute__((unused)),
  const int* inverted1 __attribute__((unused)),
  ___compose_r_withgrad__cookie_t* cookie __attribute__((unused)))
{
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__r0 npy_float64
#define item__r0(__ivar0) (*(ctype__r0*)(data_slice__r0 + (__ivar0)*strides_slice__r0[0]))
#define ctype__r1 npy_float64
#define item__r1(__ivar0) (*(ctype__r1*)(data_slice__r1 + (__ivar0)*strides_slice__r1[0]))

static
bool ___compose_r_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__r0 __attribute__((unused)),
  const npy_intp* dims_full__r0 __attribute__((unused)),
  const npy_intp* strides_full__r0 __attribute__((unused)),
  const int Ndims_slice__r0 __attribute__((unused)),
  const npy_intp* dims_slice__r0 __attribute__((unused)),
  const npy_intp* strides_slice__r0 __attribute__((unused)),
  npy_intp sizeof_element__r0 __attribute__((unused)),
  void* data_slice__r0 __attribute__((unused)),
  const int Ndims_full__r1 __attribute__((unused)),
  const npy_intp* dims_full__r1 __attribute__((unused)),
  const npy_intp* strides_full__r1 __attribute__((unused)),
  const int Ndims_slice__r1 __attribute__((unused)),
  const npy_intp* dims_slice__r1 __attribute__((unused)),
  const npy_intp* strides_slice__r1 __attribute__((unused)),
  npy_intp sizeof_element__r1 __attribute__((unused)),
  void* data_slice__r1 __attribute__((unused)),
  const int* inverted0 __attribute__((unused)),
  const int* inverted1 __attribute__((unused)),
  ___compose_r_withgrad__cookie_t* cookie __attribute__((unused)))
{

    mrcal_compose_r_full( (double*)data_slice__output0,
                           strides_slice__output0[0],

                           // dr/dr0
                           &item__output1(0,0),
                           strides_slice__output1[0], strides_slice__output1[1],

                           // dr/dr1
                           &item__output2(0,0),
                           strides_slice__output2[0], strides_slice__output2[1],

                           (const double*)data_slice__r0,
                           strides_slice__r0[0],
                           (const double*)data_slice__r1,
                           strides_slice__r1[0],
                           *inverted0, *inverted1);

    return true;

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__output2
#undef ctype__output2
#undef item__r0
#undef ctype__r0
#undef item__r1
#undef ctype__r1
#define ARGUMENTS(_) \
  _(r0) \
  _(r1)

#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___compose_r_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___compose_r_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___compose_r_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
    ___compose_r_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
    ___compose_r_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted0 __attribute__((unused)),const int* inverted1 __attribute__((unused)),___compose_r_withgrad__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted0 = false;
int inverted1 = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted0","inverted1",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p""p" ":bindings_poseutils_npsp._compose_r_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted0,&inverted1,
                                     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_r0[1] = {3};
    const npy_intp PROTOTYPE_r1[1] = {3};
    const npy_intp PROTOTYPE_output0[1] = {3};
    const npy_intp PROTOTYPE_output1[2] = {3,3};
    const npy_intp PROTOTYPE_output2[2] = {3,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(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___compose_r_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 = ___compose_r_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( ! ___compose_r_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted0,&inverted1,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)
                                  &inverted0,&inverted1,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___compose_r_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)
                                  &inverted0,&inverted1,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___compose_r_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___compose_r_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__r0
#undef CHECK_CONTIGUOUS__r0
#undef CHECK_CONTIGUOUS_AND_SETERROR__r0
#undef _CHECK_CONTIGUOUS__r1
#undef CHECK_CONTIGUOUS__r1
#undef CHECK_CONTIGUOUS_AND_SETERROR__r1

#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   compose_r_tinyr0_gradientr0

#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__r1(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__r1; i++)                               \
      if(dims_full__r1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__r1; i--)                       \
      {                                                                 \
          if(strides_slice__r1[i+Ndims_slice__r1] != sizeof_element__r1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'r1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__r1[i+Ndims_slice__r1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__r1()              _CHECK_CONTIGUOUS__r1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__r1() _CHECK_CONTIGUOUS__r1(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__r1()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__r1()

typedef struct {  } __compose_r_tinyr0_gradientr0__cookie_t;

static
bool __compose_r_tinyr0_gradientr0__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__r1 __attribute__((unused)),
  const npy_intp* dims_full__r1 __attribute__((unused)),
  const npy_intp* strides_full__r1 __attribute__((unused)),
  const int Ndims_slice__r1 __attribute__((unused)),
  const npy_intp* dims_slice__r1 __attribute__((unused)),
  const npy_intp* strides_slice__r1 __attribute__((unused)),
  npy_intp sizeof_element__r1 __attribute__((unused)),
  void* data__r1 __attribute__((unused)),
  __compose_r_tinyr0_gradientr0__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#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__r1 npy_float64
#define item__r1(__ivar0) (*(ctype__r1*)(data_slice__r1 + (__ivar0)*strides_slice__r1[0]))

static
bool __compose_r_tinyr0_gradientr0__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__r1 __attribute__((unused)),
  const npy_intp* dims_full__r1 __attribute__((unused)),
  const npy_intp* strides_full__r1 __attribute__((unused)),
  const int Ndims_slice__r1 __attribute__((unused)),
  const npy_intp* dims_slice__r1 __attribute__((unused)),
  const npy_intp* strides_slice__r1 __attribute__((unused)),
  npy_intp sizeof_element__r1 __attribute__((unused)),
  void* data_slice__r1 __attribute__((unused)),
  __compose_r_tinyr0_gradientr0__cookie_t* cookie __attribute__((unused)))
{

    mrcal_compose_r_tinyr0_gradientr0_full(
        // dr/dr0
        &item__output(0,0),
        strides_slice__output[0], strides_slice__output[1],
        (const double*)data_slice__r1,
        strides_slice__r1[0] );

    return true;

}
#undef item__output
#undef ctype__output
#undef item__r1
#undef ctype__r1
#define ARGUMENTS(_) \
  _(r1)

#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__compose_r_tinyr0_gradientr0__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__compose_r_tinyr0_gradientr0__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__compose_r_tinyr0_gradientr0(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
    __compose_r_tinyr0_gradientr0__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
    __compose_r_tinyr0_gradientr0__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) __compose_r_tinyr0_gradientr0__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_poseutils_npsp.compose_r_tinyr0_gradientr0",
                                     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_r1[1] = {3};
    const npy_intp PROTOTYPE_output[2] = {3,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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap__compose_r_tinyr0_gradientr0__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = __compose_r_tinyr0_gradientr0__ ## 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   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   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( ! __compose_r_tinyr0_gradientr0__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, "__compose_r_tinyr0_gradientr0__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,
                                 "__compose_r_tinyr0_gradientr0__slice failed!");
                goto done;
            }

        } while(__pywrap__compose_r_tinyr0_gradientr0__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__r1
#undef CHECK_CONTIGUOUS__r1
#undef CHECK_CONTIGUOUS_AND_SETERROR__r1

#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   compose_r_tinyr1_gradientr1

#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__r0(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__r0; i++)                               \
      if(dims_full__r0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__r0; i--)                       \
      {                                                                 \
          if(strides_slice__r0[i+Ndims_slice__r0] != sizeof_element__r0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'r0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__r0[i+Ndims_slice__r0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__r0()              _CHECK_CONTIGUOUS__r0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__r0() _CHECK_CONTIGUOUS__r0(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__r0()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__r0()

typedef struct {  } __compose_r_tinyr1_gradientr1__cookie_t;

static
bool __compose_r_tinyr1_gradientr1__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__r0 __attribute__((unused)),
  const npy_intp* dims_full__r0 __attribute__((unused)),
  const npy_intp* strides_full__r0 __attribute__((unused)),
  const int Ndims_slice__r0 __attribute__((unused)),
  const npy_intp* dims_slice__r0 __attribute__((unused)),
  const npy_intp* strides_slice__r0 __attribute__((unused)),
  npy_intp sizeof_element__r0 __attribute__((unused)),
  void* data__r0 __attribute__((unused)),
  __compose_r_tinyr1_gradientr1__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#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__r0 npy_float64
#define item__r0(__ivar0) (*(ctype__r0*)(data_slice__r0 + (__ivar0)*strides_slice__r0[0]))

static
bool __compose_r_tinyr1_gradientr1__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__r0 __attribute__((unused)),
  const npy_intp* dims_full__r0 __attribute__((unused)),
  const npy_intp* strides_full__r0 __attribute__((unused)),
  const int Ndims_slice__r0 __attribute__((unused)),
  const npy_intp* dims_slice__r0 __attribute__((unused)),
  const npy_intp* strides_slice__r0 __attribute__((unused)),
  npy_intp sizeof_element__r0 __attribute__((unused)),
  void* data_slice__r0 __attribute__((unused)),
  __compose_r_tinyr1_gradientr1__cookie_t* cookie __attribute__((unused)))
{

    mrcal_compose_r_tinyr1_gradientr1_full(
        // dr/dr1
        &item__output(0,0),
        strides_slice__output[0], strides_slice__output[1],
        (const double*)data_slice__r0,
        strides_slice__r0[0] );

    return true;

}
#undef item__output
#undef ctype__output
#undef item__r0
#undef ctype__r0
#define ARGUMENTS(_) \
  _(r0)

#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__compose_r_tinyr1_gradientr1__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__compose_r_tinyr1_gradientr1__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__compose_r_tinyr1_gradientr1(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
    __compose_r_tinyr1_gradientr1__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
    __compose_r_tinyr1_gradientr1__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) __compose_r_tinyr1_gradientr1__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_poseutils_npsp.compose_r_tinyr1_gradientr1",
                                     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_r0[1] = {3};
    const npy_intp PROTOTYPE_output[2] = {3,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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap__compose_r_tinyr1_gradientr1__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = __compose_r_tinyr1_gradientr1__ ## 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   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   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( ! __compose_r_tinyr1_gradientr1__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, "__compose_r_tinyr1_gradientr1__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,
                                 "__compose_r_tinyr1_gradientr1__slice failed!");
                goto done;
            }

        } while(__pywrap__compose_r_tinyr1_gradientr1__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__r0
#undef CHECK_CONTIGUOUS__r0
#undef CHECK_CONTIGUOUS_AND_SETERROR__r0

#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   _compose_rt

#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__rt0(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__rt0; i++)                               \
      if(dims_full__rt0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__rt0; i--)                       \
      {                                                                 \
          if(strides_slice__rt0[i+Ndims_slice__rt0] != sizeof_element__rt0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'rt0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__rt0[i+Ndims_slice__rt0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__rt0()              _CHECK_CONTIGUOUS__rt0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__rt0() _CHECK_CONTIGUOUS__rt0(true)


#define _CHECK_CONTIGUOUS__rt1(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__rt1; i++)                               \
      if(dims_full__rt1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__rt1; i--)                       \
      {                                                                 \
          if(strides_slice__rt1[i+Ndims_slice__rt1] != sizeof_element__rt1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'rt1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__rt1[i+Ndims_slice__rt1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__rt1()              _CHECK_CONTIGUOUS__rt1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__rt1() _CHECK_CONTIGUOUS__rt1(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__rt0() && CHECK_CONTIGUOUS__rt1()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__rt0() && CHECK_CONTIGUOUS_AND_SETERROR__rt1()

typedef struct {  } ___compose_rt__cookie_t;

static
bool ___compose_rt__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__rt0 __attribute__((unused)),
  const npy_intp* dims_full__rt0 __attribute__((unused)),
  const npy_intp* strides_full__rt0 __attribute__((unused)),
  const int Ndims_slice__rt0 __attribute__((unused)),
  const npy_intp* dims_slice__rt0 __attribute__((unused)),
  const npy_intp* strides_slice__rt0 __attribute__((unused)),
  npy_intp sizeof_element__rt0 __attribute__((unused)),
  void* data__rt0 __attribute__((unused)),
  const int Ndims_full__rt1 __attribute__((unused)),
  const npy_intp* dims_full__rt1 __attribute__((unused)),
  const npy_intp* strides_full__rt1 __attribute__((unused)),
  const int Ndims_slice__rt1 __attribute__((unused)),
  const npy_intp* dims_slice__rt1 __attribute__((unused)),
  const npy_intp* strides_slice__rt1 __attribute__((unused)),
  npy_intp sizeof_element__rt1 __attribute__((unused)),
  void* data__rt1 __attribute__((unused)),
  const int* inverted0 __attribute__((unused)),
  const int* inverted1 __attribute__((unused)),
  ___compose_rt__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__rt0 npy_float64
#define item__rt0(__ivar0) (*(ctype__rt0*)(data_slice__rt0 + (__ivar0)*strides_slice__rt0[0]))
#define ctype__rt1 npy_float64
#define item__rt1(__ivar0) (*(ctype__rt1*)(data_slice__rt1 + (__ivar0)*strides_slice__rt1[0]))

static
bool ___compose_rt__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__rt0 __attribute__((unused)),
  const npy_intp* dims_full__rt0 __attribute__((unused)),
  const npy_intp* strides_full__rt0 __attribute__((unused)),
  const int Ndims_slice__rt0 __attribute__((unused)),
  const npy_intp* dims_slice__rt0 __attribute__((unused)),
  const npy_intp* strides_slice__rt0 __attribute__((unused)),
  npy_intp sizeof_element__rt0 __attribute__((unused)),
  void* data_slice__rt0 __attribute__((unused)),
  const int Ndims_full__rt1 __attribute__((unused)),
  const npy_intp* dims_full__rt1 __attribute__((unused)),
  const npy_intp* strides_full__rt1 __attribute__((unused)),
  const int Ndims_slice__rt1 __attribute__((unused)),
  const npy_intp* dims_slice__rt1 __attribute__((unused)),
  const npy_intp* strides_slice__rt1 __attribute__((unused)),
  npy_intp sizeof_element__rt1 __attribute__((unused)),
  void* data_slice__rt1 __attribute__((unused)),
  const int* inverted0 __attribute__((unused)),
  const int* inverted1 __attribute__((unused)),
  ___compose_rt__cookie_t* cookie __attribute__((unused)))
{

    mrcal_compose_rt_full( (double*)data_slice__output,
                           strides_slice__output[0],
                           NULL,0,0,
                           NULL,0,0,
                           NULL,0,0,
                           NULL,0,0,
                           NULL,0,0,
                           NULL,0,0,
                           (const double*)data_slice__rt0,
                           strides_slice__rt0[0],
                           (const double*)data_slice__rt1,
                           strides_slice__rt1[0],
                           *inverted0, *inverted1);
    return true;

}
#undef item__output
#undef ctype__output
#undef item__rt0
#undef ctype__rt0
#undef item__rt1
#undef ctype__rt1
#define ARGUMENTS(_) \
  _(rt0) \
  _(rt1)

#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___compose_rt__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___compose_rt__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___compose_rt(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
    ___compose_rt__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
    ___compose_rt__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted0 __attribute__((unused)),const int* inverted1 __attribute__((unused)),___compose_rt__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted0 = false;
int inverted1 = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted0","inverted1",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p""p" ":bindings_poseutils_npsp._compose_rt",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted0,&inverted1,
                                     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_rt0[1] = {6};
    const npy_intp PROTOTYPE_rt1[1] = {6};
    const npy_intp PROTOTYPE_output[1] = {6};
    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___compose_rt__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___compose_rt__ ## 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( ! ___compose_rt__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted0,&inverted1,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)
                                  &inverted0,&inverted1,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___compose_rt__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)
                                  &inverted0,&inverted1,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___compose_rt__slice failed!");
                goto done;
            }

        } while(__pywrap___compose_rt__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__rt0
#undef CHECK_CONTIGUOUS__rt0
#undef CHECK_CONTIGUOUS_AND_SETERROR__rt0
#undef _CHECK_CONTIGUOUS__rt1
#undef CHECK_CONTIGUOUS__rt1
#undef CHECK_CONTIGUOUS_AND_SETERROR__rt1

#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   _compose_rt_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__rt0(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__rt0; i++)                               \
      if(dims_full__rt0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__rt0; i--)                       \
      {                                                                 \
          if(strides_slice__rt0[i+Ndims_slice__rt0] != sizeof_element__rt0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'rt0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__rt0[i+Ndims_slice__rt0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__rt0()              _CHECK_CONTIGUOUS__rt0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__rt0() _CHECK_CONTIGUOUS__rt0(true)


#define _CHECK_CONTIGUOUS__rt1(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__rt1; i++)                               \
      if(dims_full__rt1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__rt1; i--)                       \
      {                                                                 \
          if(strides_slice__rt1[i+Ndims_slice__rt1] != sizeof_element__rt1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'rt1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__rt1[i+Ndims_slice__rt1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__rt1()              _CHECK_CONTIGUOUS__rt1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__rt1() _CHECK_CONTIGUOUS__rt1(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output0() && CHECK_CONTIGUOUS__output1() && CHECK_CONTIGUOUS__output2() && CHECK_CONTIGUOUS__rt0() && CHECK_CONTIGUOUS__rt1()
#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__rt0() && CHECK_CONTIGUOUS_AND_SETERROR__rt1()

typedef struct {  } ___compose_rt_withgrad__cookie_t;

static
bool ___compose_rt_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__rt0 __attribute__((unused)),
  const npy_intp* dims_full__rt0 __attribute__((unused)),
  const npy_intp* strides_full__rt0 __attribute__((unused)),
  const int Ndims_slice__rt0 __attribute__((unused)),
  const npy_intp* dims_slice__rt0 __attribute__((unused)),
  const npy_intp* strides_slice__rt0 __attribute__((unused)),
  npy_intp sizeof_element__rt0 __attribute__((unused)),
  void* data__rt0 __attribute__((unused)),
  const int Ndims_full__rt1 __attribute__((unused)),
  const npy_intp* dims_full__rt1 __attribute__((unused)),
  const npy_intp* strides_full__rt1 __attribute__((unused)),
  const int Ndims_slice__rt1 __attribute__((unused)),
  const npy_intp* dims_slice__rt1 __attribute__((unused)),
  const npy_intp* strides_slice__rt1 __attribute__((unused)),
  npy_intp sizeof_element__rt1 __attribute__((unused)),
  void* data__rt1 __attribute__((unused)),
  const int* inverted0 __attribute__((unused)),
  const int* inverted1 __attribute__((unused)),
  ___compose_rt_withgrad__cookie_t* cookie __attribute__((unused)))
{
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__rt0 npy_float64
#define item__rt0(__ivar0) (*(ctype__rt0*)(data_slice__rt0 + (__ivar0)*strides_slice__rt0[0]))
#define ctype__rt1 npy_float64
#define item__rt1(__ivar0) (*(ctype__rt1*)(data_slice__rt1 + (__ivar0)*strides_slice__rt1[0]))

static
bool ___compose_rt_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__rt0 __attribute__((unused)),
  const npy_intp* dims_full__rt0 __attribute__((unused)),
  const npy_intp* strides_full__rt0 __attribute__((unused)),
  const int Ndims_slice__rt0 __attribute__((unused)),
  const npy_intp* dims_slice__rt0 __attribute__((unused)),
  const npy_intp* strides_slice__rt0 __attribute__((unused)),
  npy_intp sizeof_element__rt0 __attribute__((unused)),
  void* data_slice__rt0 __attribute__((unused)),
  const int Ndims_full__rt1 __attribute__((unused)),
  const npy_intp* dims_full__rt1 __attribute__((unused)),
  const npy_intp* strides_full__rt1 __attribute__((unused)),
  const int Ndims_slice__rt1 __attribute__((unused)),
  const npy_intp* dims_slice__rt1 __attribute__((unused)),
  const npy_intp* strides_slice__rt1 __attribute__((unused)),
  npy_intp sizeof_element__rt1 __attribute__((unused)),
  void* data_slice__rt1 __attribute__((unused)),
  const int* inverted0 __attribute__((unused)),
  const int* inverted1 __attribute__((unused)),
  ___compose_rt_withgrad__cookie_t* cookie __attribute__((unused)))
{

    mrcal_compose_rt_full( (double*)data_slice__output0,
                           strides_slice__output0[0],

                           // dr/dr0
                           &item__output1(0,0),
                           strides_slice__output1[0], strides_slice__output1[1],

                           // dr/dr1
                           &item__output2(0,0),
                           strides_slice__output2[0], strides_slice__output2[1],

                           // dt/dr0
                           &item__output1(3,0),
                           strides_slice__output1[0], strides_slice__output1[1],

                           // dt/dr1
                           &item__output2(3,0),
                           strides_slice__output2[0], strides_slice__output2[1],

                           // dt/dt0
                           &item__output1(3,3),
                           strides_slice__output1[0], strides_slice__output1[1],

                           // dt/dt1
                           &item__output2(3,3),
                           strides_slice__output2[0], strides_slice__output2[1],

                           (const double*)data_slice__rt0,
                           strides_slice__rt0[0],
                           (const double*)data_slice__rt1,
                           strides_slice__rt1[0],
                           *inverted0, *inverted1 );
    for(int i=0; i<3; i++)
        for(int j=0; j<3; j++)
        {
            item__output1(i,  j+3) = 0;
            item__output2(i,  j+3) = 0;
        }

    return true;

}
#undef item__output0
#undef ctype__output0
#undef item__output1
#undef ctype__output1
#undef item__output2
#undef ctype__output2
#undef item__rt0
#undef ctype__rt0
#undef item__rt1
#undef ctype__rt1
#define ARGUMENTS(_) \
  _(rt0) \
  _(rt1)

#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___compose_rt_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___compose_rt_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___compose_rt_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
    ___compose_rt_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
    ___compose_rt_withgrad__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) const int* inverted0 __attribute__((unused)),const int* inverted1 __attribute__((unused)),___compose_rt_withgrad__cookie_t* cookie __attribute__((unused)));


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

    ARGUMENTS(ARG_DEFINE);
    OUTPUTS(  ARG_DEFINE);
    int inverted0 = false;
int inverted1 = false;
;

    SET_SIGINT();

#define NAMELIST(name) #name ,
    char* keywords[] = { ARGUMENTS(NAMELIST) "out",
                         "inverted0","inverted1",
                         NULL };
#define PARSECODE(name) "O&"
#define PARSEARG(name) PyArray_Converter, &__py__ ## name,
    if(!PyArg_ParseTupleAndKeywords( args, kwargs,
                                     ARGUMENTS(PARSECODE) "|O" "p""p" ":bindings_poseutils_npsp._compose_rt_withgrad",
                                     keywords,
                                     ARGUMENTS(PARSEARG)
                                     &__py__output__arg,
                                     &inverted0,&inverted1,
                                     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_rt0[1] = {6};
    const npy_intp PROTOTYPE_rt1[1] = {6};
    const npy_intp PROTOTYPE_output0[1] = {6};
    const npy_intp PROTOTYPE_output1[2] = {6,6};
    const npy_intp PROTOTYPE_output2[2] = {6,6};
    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(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___compose_rt_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 = ___compose_rt_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( ! ___compose_rt_withgrad__validate(OUTPUTS(  ARGLIST_CALL_USER_CALLBACK)
                                          ARGUMENTS(ARGLIST_CALL_USER_CALLBACK)
                                          &inverted0,&inverted1,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)
                                  &inverted0,&inverted1,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError, "___compose_rt_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)
                                  &inverted0,&inverted1,cookie) )
            {
                if(PyErr_Occurred() == NULL)
                    PyErr_Format(PyExc_RuntimeError,
                                 "___compose_rt_withgrad__slice failed!");
                goto done;
            }

        } while(__pywrap___compose_rt_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__rt0
#undef CHECK_CONTIGUOUS__rt0
#undef CHECK_CONTIGUOUS_AND_SETERROR__rt0
#undef _CHECK_CONTIGUOUS__rt1
#undef CHECK_CONTIGUOUS__rt1
#undef CHECK_CONTIGUOUS_AND_SETERROR__rt1

#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   R_from_quat

#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__q(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__q; i++)                               \
      if(dims_full__q[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__q; i--)                       \
      {                                                                 \
          if(strides_slice__q[i+Ndims_slice__q] != sizeof_element__q*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'q' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__q[i+Ndims_slice__q];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__q()              _CHECK_CONTIGUOUS__q(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__q() _CHECK_CONTIGUOUS__q(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__q()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__q()

typedef struct {  } __R_from_quat__cookie_t;

static
bool __R_from_quat__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__q __attribute__((unused)),
  const npy_intp* dims_full__q __attribute__((unused)),
  const npy_intp* strides_full__q __attribute__((unused)),
  const int Ndims_slice__q __attribute__((unused)),
  const npy_intp* dims_slice__q __attribute__((unused)),
  const npy_intp* strides_slice__q __attribute__((unused)),
  npy_intp sizeof_element__q __attribute__((unused)),
  void* data__q __attribute__((unused)),
  __R_from_quat__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#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__q npy_float64
#define item__q(__ivar0) (*(ctype__q*)(data_slice__q + (__ivar0)*strides_slice__q[0]))

static
bool __R_from_quat__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__q __attribute__((unused)),
  const npy_intp* dims_full__q __attribute__((unused)),
  const npy_intp* strides_full__q __attribute__((unused)),
  const int Ndims_slice__q __attribute__((unused)),
  const npy_intp* dims_slice__q __attribute__((unused)),
  const npy_intp* strides_slice__q __attribute__((unused)),
  npy_intp sizeof_element__q __attribute__((unused)),
  void* data_slice__q __attribute__((unused)),
  __R_from_quat__cookie_t* cookie __attribute__((unused)))
{

    // From the expression in wikipedia
    const double r = item__q(0);
    const double i = item__q(1);
    const double j = item__q(2);
    const double k = item__q(3);

    const double ii = i*i;
    const double ij = i*j;
    const double ik = i*k;
    const double ir = i*r;
    const double jj = j*j;
    const double jk = j*k;
    const double jr = j*r;
    const double kk = k*k;
    const double kr = k*r;

    item__output(0,0) = 1. - 2.*(jj+kk);
    item__output(0,1) =      2.*(ij-kr);
    item__output(0,2) =      2.*(ik+jr);

    item__output(1,0) =      2.*(ij+kr);
    item__output(1,1) = 1. - 2.*(ii+kk);
    item__output(1,2) =      2.*(jk-ir);

    item__output(2,0) =      2.*(ik-jr);
    item__output(2,1) =      2.*(jk+ir);
    item__output(2,2) = 1. - 2.*(ii+jj);

    return true;

}
#undef item__output
#undef ctype__output
#undef item__q
#undef ctype__q
#define ARGUMENTS(_) \
  _(q)

#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__R_from_quat__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__R_from_quat__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__R_from_quat(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
    __R_from_quat__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
    __R_from_quat__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) __R_from_quat__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_poseutils_npsp.R_from_quat",
                                     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_q[1] = {4};
    const npy_intp PROTOTYPE_output[2] = {3,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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap__R_from_quat__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = __R_from_quat__ ## 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   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   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( ! __R_from_quat__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, "__R_from_quat__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,
                                 "__R_from_quat__slice failed!");
                goto done;
            }

        } while(__pywrap__R_from_quat__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__q
#undef CHECK_CONTIGUOUS__q
#undef CHECK_CONTIGUOUS_AND_SETERROR__q

#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   skew_symmetric

#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_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__a()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__a()

typedef struct {  } __skew_symmetric__cookie_t;

static
bool __skew_symmetric__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)),
  __skew_symmetric__cookie_t* cookie __attribute__((unused)))
{
return true;
}

#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_float64
#define item__a(__ivar0) (*(ctype__a*)(data_slice__a + (__ivar0)*strides_slice__a[0]))

static
bool __skew_symmetric__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)),
  __skew_symmetric__cookie_t* cookie __attribute__((unused)))
{

    // diagonal is zero
    item__output(0,0) = 0.0;
    item__output(1,1) = 0.0;
    item__output(2,2) = 0.0;

    item__output(0,1) = -item__a(2);
    item__output(0,2) =  item__a(1);
    item__output(1,0) =  item__a(2);
    item__output(1,2) = -item__a(0);
    item__output(2,0) = -item__a(1);
    item__output(2,1) =  item__a(0);

    return true;

}
#undef item__output
#undef ctype__output
#undef item__a
#undef ctype__a
#define ARGUMENTS(_) \
  _(a)

#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__skew_symmetric__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__skew_symmetric__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__skew_symmetric(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
    __skew_symmetric__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
    __skew_symmetric__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) __skew_symmetric__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_poseutils_npsp.skew_symmetric",
                                     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_a[1] = {3};
    const npy_intp PROTOTYPE_output[2] = {3,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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap__skew_symmetric__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = __skew_symmetric__ ## 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   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   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( ! __skew_symmetric__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, "__skew_symmetric__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,
                                 "__skew_symmetric__slice failed!");
                goto done;
            }

        } while(__pywrap__skew_symmetric__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_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   _align_procrustes_vectors_R01_weights

#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__v0(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__v0; i++)                               \
      if(dims_full__v0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__v0; i--)                       \
      {                                                                 \
          if(strides_slice__v0[i+Ndims_slice__v0] != sizeof_element__v0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'v0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__v0[i+Ndims_slice__v0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__v0()              _CHECK_CONTIGUOUS__v0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__v0() _CHECK_CONTIGUOUS__v0(true)


#define _CHECK_CONTIGUOUS__v1(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__v1; i++)                               \
      if(dims_full__v1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__v1; i--)                       \
      {                                                                 \
          if(strides_slice__v1[i+Ndims_slice__v1] != sizeof_element__v1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'v1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__v1[i+Ndims_slice__v1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__v1()              _CHECK_CONTIGUOUS__v1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__v1() _CHECK_CONTIGUOUS__v1(true)


#define _CHECK_CONTIGUOUS__weights(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__weights; i++)                               \
      if(dims_full__weights[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__weights; i--)                       \
      {                                                                 \
          if(strides_slice__weights[i+Ndims_slice__weights] != sizeof_element__weights*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'weights' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__weights[i+Ndims_slice__weights];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__weights()              _CHECK_CONTIGUOUS__weights(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__weights() _CHECK_CONTIGUOUS__weights(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__v0() && CHECK_CONTIGUOUS__v1() && CHECK_CONTIGUOUS__weights()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__v0() && CHECK_CONTIGUOUS_AND_SETERROR__v1() && CHECK_CONTIGUOUS_AND_SETERROR__weights()

typedef struct {  } ___align_procrustes_vectors_R01_weights__cookie_t;

static
bool ___align_procrustes_vectors_R01_weights__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__v0 __attribute__((unused)),
  const npy_intp* dims_full__v0 __attribute__((unused)),
  const npy_intp* strides_full__v0 __attribute__((unused)),
  const int Ndims_slice__v0 __attribute__((unused)),
  const npy_intp* dims_slice__v0 __attribute__((unused)),
  const npy_intp* strides_slice__v0 __attribute__((unused)),
  npy_intp sizeof_element__v0 __attribute__((unused)),
  void* data__v0 __attribute__((unused)),
  const int Ndims_full__v1 __attribute__((unused)),
  const npy_intp* dims_full__v1 __attribute__((unused)),
  const npy_intp* strides_full__v1 __attribute__((unused)),
  const int Ndims_slice__v1 __attribute__((unused)),
  const npy_intp* dims_slice__v1 __attribute__((unused)),
  const npy_intp* strides_slice__v1 __attribute__((unused)),
  npy_intp sizeof_element__v1 __attribute__((unused)),
  void* data__v1 __attribute__((unused)),
  const int Ndims_full__weights __attribute__((unused)),
  const npy_intp* dims_full__weights __attribute__((unused)),
  const npy_intp* strides_full__weights __attribute__((unused)),
  const int Ndims_slice__weights __attribute__((unused)),
  const npy_intp* dims_slice__weights __attribute__((unused)),
  const npy_intp* strides_slice__weights __attribute__((unused)),
  npy_intp sizeof_element__weights __attribute__((unused)),
  void* data__weights __attribute__((unused)),
  ___align_procrustes_vectors_R01_weights__cookie_t* cookie __attribute__((unused)))
{

                    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__v0 npy_float64
#define item__v0(__ivar0,__ivar1) (*(ctype__v0*)(data_slice__v0 + (__ivar0)*strides_slice__v0[0]+ (__ivar1)*strides_slice__v0[1]))
#define ctype__v1 npy_float64
#define item__v1(__ivar0,__ivar1) (*(ctype__v1*)(data_slice__v1 + (__ivar0)*strides_slice__v1[0]+ (__ivar1)*strides_slice__v1[1]))
#define ctype__weights npy_float64
#define item__weights(__ivar0) (*(ctype__weights*)(data_slice__weights + (__ivar0)*strides_slice__weights[0]))

static
bool ___align_procrustes_vectors_R01_weights__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__v0 __attribute__((unused)),
  const npy_intp* dims_full__v0 __attribute__((unused)),
  const npy_intp* strides_full__v0 __attribute__((unused)),
  const int Ndims_slice__v0 __attribute__((unused)),
  const npy_intp* dims_slice__v0 __attribute__((unused)),
  const npy_intp* strides_slice__v0 __attribute__((unused)),
  npy_intp sizeof_element__v0 __attribute__((unused)),
  void* data_slice__v0 __attribute__((unused)),
  const int Ndims_full__v1 __attribute__((unused)),
  const npy_intp* dims_full__v1 __attribute__((unused)),
  const npy_intp* strides_full__v1 __attribute__((unused)),
  const int Ndims_slice__v1 __attribute__((unused)),
  const npy_intp* dims_slice__v1 __attribute__((unused)),
  const npy_intp* strides_slice__v1 __attribute__((unused)),
  npy_intp sizeof_element__v1 __attribute__((unused)),
  void* data_slice__v1 __attribute__((unused)),
  const int Ndims_full__weights __attribute__((unused)),
  const npy_intp* dims_full__weights __attribute__((unused)),
  const npy_intp* strides_full__weights __attribute__((unused)),
  const int Ndims_slice__weights __attribute__((unused)),
  const npy_intp* dims_slice__weights __attribute__((unused)),
  const npy_intp* strides_slice__weights __attribute__((unused)),
  npy_intp sizeof_element__weights __attribute__((unused)),
  void* data_slice__weights __attribute__((unused)),
  ___align_procrustes_vectors_R01_weights__cookie_t* cookie __attribute__((unused)))
{

            bool result =
            mrcal_align_procrustes_vectors_R01((double*)data_slice__output,
                                                 dims_slice__v0[0],
                                                 (double*)data_slice__v0,
                                                 (double*)data_slice__v1,
                                                 (double*)data_slice__weights);

            if(!result && 0.0 == *(double*)data_slice__output)
            {

                // Poorly-defined problem. I indicate this with an all-zero
                // output, but I return true. This allows us to process
                // lots of data via broadcasting, without breaking ALL
                // the slices if one slice is broken
                memset((double*)data_slice__output, 0, 9*sizeof(double));
                return true;
            }
            return result;
        
}
#undef item__output
#undef ctype__output
#undef item__v0
#undef ctype__v0
#undef item__v1
#undef ctype__v1
#undef item__weights
#undef ctype__weights
#define ARGUMENTS(_) \
  _(v0) \
  _(v1) \
  _(weights)

#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___align_procrustes_vectors_R01_weights__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___align_procrustes_vectors_R01_weights__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___align_procrustes_vectors_R01_weights(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
    ___align_procrustes_vectors_R01_weights__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
    ___align_procrustes_vectors_R01_weights__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___align_procrustes_vectors_R01_weights__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_poseutils_npsp._align_procrustes_vectors_R01_weights",
                                     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_v0[2] = {-1,3};
    const npy_intp PROTOTYPE_v1[2] = {-1,3};
    const npy_intp PROTOTYPE_weights[1] = {-1};
    const npy_intp PROTOTYPE_output[2] = {3,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,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3, i)                   \
        else if( __pywrap___align_procrustes_vectors_R01_weights__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___align_procrustes_vectors_R01_weights__ ## 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   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   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( ! ___align_procrustes_vectors_R01_weights__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, "___align_procrustes_vectors_R01_weights__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,
                                 "___align_procrustes_vectors_R01_weights__slice failed!");
                goto done;
            }

        } while(__pywrap___align_procrustes_vectors_R01_weights__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__v0
#undef CHECK_CONTIGUOUS__v0
#undef CHECK_CONTIGUOUS_AND_SETERROR__v0
#undef _CHECK_CONTIGUOUS__v1
#undef CHECK_CONTIGUOUS__v1
#undef CHECK_CONTIGUOUS_AND_SETERROR__v1
#undef _CHECK_CONTIGUOUS__weights
#undef CHECK_CONTIGUOUS__weights
#undef CHECK_CONTIGUOUS_AND_SETERROR__weights

#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   _align_procrustes_points_Rt01_weights

#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__v0(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__v0; i++)                               \
      if(dims_full__v0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__v0; i--)                       \
      {                                                                 \
          if(strides_slice__v0[i+Ndims_slice__v0] != sizeof_element__v0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'v0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__v0[i+Ndims_slice__v0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__v0()              _CHECK_CONTIGUOUS__v0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__v0() _CHECK_CONTIGUOUS__v0(true)


#define _CHECK_CONTIGUOUS__v1(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__v1; i++)                               \
      if(dims_full__v1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__v1; i--)                       \
      {                                                                 \
          if(strides_slice__v1[i+Ndims_slice__v1] != sizeof_element__v1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'v1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__v1[i+Ndims_slice__v1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__v1()              _CHECK_CONTIGUOUS__v1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__v1() _CHECK_CONTIGUOUS__v1(true)


#define _CHECK_CONTIGUOUS__weights(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__weights; i++)                               \
      if(dims_full__weights[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__weights; i--)                       \
      {                                                                 \
          if(strides_slice__weights[i+Ndims_slice__weights] != sizeof_element__weights*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'weights' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__weights[i+Ndims_slice__weights];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__weights()              _CHECK_CONTIGUOUS__weights(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__weights() _CHECK_CONTIGUOUS__weights(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__v0() && CHECK_CONTIGUOUS__v1() && CHECK_CONTIGUOUS__weights()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__v0() && CHECK_CONTIGUOUS_AND_SETERROR__v1() && CHECK_CONTIGUOUS_AND_SETERROR__weights()

typedef struct {  } ___align_procrustes_points_Rt01_weights__cookie_t;

static
bool ___align_procrustes_points_Rt01_weights__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__v0 __attribute__((unused)),
  const npy_intp* dims_full__v0 __attribute__((unused)),
  const npy_intp* strides_full__v0 __attribute__((unused)),
  const int Ndims_slice__v0 __attribute__((unused)),
  const npy_intp* dims_slice__v0 __attribute__((unused)),
  const npy_intp* strides_slice__v0 __attribute__((unused)),
  npy_intp sizeof_element__v0 __attribute__((unused)),
  void* data__v0 __attribute__((unused)),
  const int Ndims_full__v1 __attribute__((unused)),
  const npy_intp* dims_full__v1 __attribute__((unused)),
  const npy_intp* strides_full__v1 __attribute__((unused)),
  const int Ndims_slice__v1 __attribute__((unused)),
  const npy_intp* dims_slice__v1 __attribute__((unused)),
  const npy_intp* strides_slice__v1 __attribute__((unused)),
  npy_intp sizeof_element__v1 __attribute__((unused)),
  void* data__v1 __attribute__((unused)),
  const int Ndims_full__weights __attribute__((unused)),
  const npy_intp* dims_full__weights __attribute__((unused)),
  const npy_intp* strides_full__weights __attribute__((unused)),
  const int Ndims_slice__weights __attribute__((unused)),
  const npy_intp* dims_slice__weights __attribute__((unused)),
  const npy_intp* strides_slice__weights __attribute__((unused)),
  npy_intp sizeof_element__weights __attribute__((unused)),
  void* data__weights __attribute__((unused)),
  ___align_procrustes_points_Rt01_weights__cookie_t* cookie __attribute__((unused)))
{

                    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__v0 npy_float64
#define item__v0(__ivar0,__ivar1) (*(ctype__v0*)(data_slice__v0 + (__ivar0)*strides_slice__v0[0]+ (__ivar1)*strides_slice__v0[1]))
#define ctype__v1 npy_float64
#define item__v1(__ivar0,__ivar1) (*(ctype__v1*)(data_slice__v1 + (__ivar0)*strides_slice__v1[0]+ (__ivar1)*strides_slice__v1[1]))
#define ctype__weights npy_float64
#define item__weights(__ivar0) (*(ctype__weights*)(data_slice__weights + (__ivar0)*strides_slice__weights[0]))

static
bool ___align_procrustes_points_Rt01_weights__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__v0 __attribute__((unused)),
  const npy_intp* dims_full__v0 __attribute__((unused)),
  const npy_intp* strides_full__v0 __attribute__((unused)),
  const int Ndims_slice__v0 __attribute__((unused)),
  const npy_intp* dims_slice__v0 __attribute__((unused)),
  const npy_intp* strides_slice__v0 __attribute__((unused)),
  npy_intp sizeof_element__v0 __attribute__((unused)),
  void* data_slice__v0 __attribute__((unused)),
  const int Ndims_full__v1 __attribute__((unused)),
  const npy_intp* dims_full__v1 __attribute__((unused)),
  const npy_intp* strides_full__v1 __attribute__((unused)),
  const int Ndims_slice__v1 __attribute__((unused)),
  const npy_intp* dims_slice__v1 __attribute__((unused)),
  const npy_intp* strides_slice__v1 __attribute__((unused)),
  npy_intp sizeof_element__v1 __attribute__((unused)),
  void* data_slice__v1 __attribute__((unused)),
  const int Ndims_full__weights __attribute__((unused)),
  const npy_intp* dims_full__weights __attribute__((unused)),
  const npy_intp* strides_full__weights __attribute__((unused)),
  const int Ndims_slice__weights __attribute__((unused)),
  const npy_intp* dims_slice__weights __attribute__((unused)),
  const npy_intp* strides_slice__weights __attribute__((unused)),
  npy_intp sizeof_element__weights __attribute__((unused)),
  void* data_slice__weights __attribute__((unused)),
  ___align_procrustes_points_Rt01_weights__cookie_t* cookie __attribute__((unused)))
{

            bool result =
            mrcal_align_procrustes_points_Rt01((double*)data_slice__output,
                                                 dims_slice__v0[0],
                                                 (double*)data_slice__v0,
                                                 (double*)data_slice__v1,
                                                 (double*)data_slice__weights);

            if(!result && 0.0 == *(double*)data_slice__output)
            {

                // Poorly-defined problem. I indicate this with an all-zero
                // output, but I return true. This allows us to process
                // lots of data via broadcasting, without breaking ALL
                // the slices if one slice is broken
                memset((double*)data_slice__output, 0, 12*sizeof(double));
                return true;
            }
            return result;
        
}
#undef item__output
#undef ctype__output
#undef item__v0
#undef ctype__v0
#undef item__v1
#undef ctype__v1
#undef item__weights
#undef ctype__weights
#define ARGUMENTS(_) \
  _(v0) \
  _(v1) \
  _(weights)

#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___align_procrustes_points_Rt01_weights__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___align_procrustes_points_Rt01_weights__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___align_procrustes_points_Rt01_weights(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
    ___align_procrustes_points_Rt01_weights__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
    ___align_procrustes_points_Rt01_weights__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___align_procrustes_points_Rt01_weights__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_poseutils_npsp._align_procrustes_points_Rt01_weights",
                                     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_v0[2] = {-1,3};
    const npy_intp PROTOTYPE_v1[2] = {-1,3};
    const npy_intp PROTOTYPE_weights[1] = {-1};
    const npy_intp PROTOTYPE_output[2] = {4,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,12,0)
#define TYPESET_MATCHES(t0,t1,t2,t3, i)                   \
        else if( __pywrap___align_procrustes_points_Rt01_weights__type_matches                \
                 (                                                      \
                             t0,t1,t2,t3,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___align_procrustes_points_Rt01_weights__ ## 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   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   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( ! ___align_procrustes_points_Rt01_weights__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, "___align_procrustes_points_Rt01_weights__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,
                                 "___align_procrustes_points_Rt01_weights__slice failed!");
                goto done;
            }

        } while(__pywrap___align_procrustes_points_Rt01_weights__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__v0
#undef CHECK_CONTIGUOUS__v0
#undef CHECK_CONTIGUOUS_AND_SETERROR__v0
#undef _CHECK_CONTIGUOUS__v1
#undef CHECK_CONTIGUOUS__v1
#undef CHECK_CONTIGUOUS_AND_SETERROR__v1
#undef _CHECK_CONTIGUOUS__weights
#undef CHECK_CONTIGUOUS__weights
#undef CHECK_CONTIGUOUS_AND_SETERROR__weights

#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   _align_procrustes_vectors_R01_noweights

#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__v0(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__v0; i++)                               \
      if(dims_full__v0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__v0; i--)                       \
      {                                                                 \
          if(strides_slice__v0[i+Ndims_slice__v0] != sizeof_element__v0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'v0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__v0[i+Ndims_slice__v0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__v0()              _CHECK_CONTIGUOUS__v0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__v0() _CHECK_CONTIGUOUS__v0(true)


#define _CHECK_CONTIGUOUS__v1(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__v1; i++)                               \
      if(dims_full__v1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__v1; i--)                       \
      {                                                                 \
          if(strides_slice__v1[i+Ndims_slice__v1] != sizeof_element__v1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'v1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__v1[i+Ndims_slice__v1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__v1()              _CHECK_CONTIGUOUS__v1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__v1() _CHECK_CONTIGUOUS__v1(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__v0() && CHECK_CONTIGUOUS__v1()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__v0() && CHECK_CONTIGUOUS_AND_SETERROR__v1()

typedef struct {  } ___align_procrustes_vectors_R01_noweights__cookie_t;

static
bool ___align_procrustes_vectors_R01_noweights__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__v0 __attribute__((unused)),
  const npy_intp* dims_full__v0 __attribute__((unused)),
  const npy_intp* strides_full__v0 __attribute__((unused)),
  const int Ndims_slice__v0 __attribute__((unused)),
  const npy_intp* dims_slice__v0 __attribute__((unused)),
  const npy_intp* strides_slice__v0 __attribute__((unused)),
  npy_intp sizeof_element__v0 __attribute__((unused)),
  void* data__v0 __attribute__((unused)),
  const int Ndims_full__v1 __attribute__((unused)),
  const npy_intp* dims_full__v1 __attribute__((unused)),
  const npy_intp* strides_full__v1 __attribute__((unused)),
  const int Ndims_slice__v1 __attribute__((unused)),
  const npy_intp* dims_slice__v1 __attribute__((unused)),
  const npy_intp* strides_slice__v1 __attribute__((unused)),
  npy_intp sizeof_element__v1 __attribute__((unused)),
  void* data__v1 __attribute__((unused)),
  ___align_procrustes_vectors_R01_noweights__cookie_t* cookie __attribute__((unused)))
{

                    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__v0 npy_float64
#define item__v0(__ivar0,__ivar1) (*(ctype__v0*)(data_slice__v0 + (__ivar0)*strides_slice__v0[0]+ (__ivar1)*strides_slice__v0[1]))
#define ctype__v1 npy_float64
#define item__v1(__ivar0,__ivar1) (*(ctype__v1*)(data_slice__v1 + (__ivar0)*strides_slice__v1[0]+ (__ivar1)*strides_slice__v1[1]))

static
bool ___align_procrustes_vectors_R01_noweights__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__v0 __attribute__((unused)),
  const npy_intp* dims_full__v0 __attribute__((unused)),
  const npy_intp* strides_full__v0 __attribute__((unused)),
  const int Ndims_slice__v0 __attribute__((unused)),
  const npy_intp* dims_slice__v0 __attribute__((unused)),
  const npy_intp* strides_slice__v0 __attribute__((unused)),
  npy_intp sizeof_element__v0 __attribute__((unused)),
  void* data_slice__v0 __attribute__((unused)),
  const int Ndims_full__v1 __attribute__((unused)),
  const npy_intp* dims_full__v1 __attribute__((unused)),
  const npy_intp* strides_full__v1 __attribute__((unused)),
  const int Ndims_slice__v1 __attribute__((unused)),
  const npy_intp* dims_slice__v1 __attribute__((unused)),
  const npy_intp* strides_slice__v1 __attribute__((unused)),
  npy_intp sizeof_element__v1 __attribute__((unused)),
  void* data_slice__v1 __attribute__((unused)),
  ___align_procrustes_vectors_R01_noweights__cookie_t* cookie __attribute__((unused)))
{

            bool result =
            mrcal_align_procrustes_vectors_R01((double*)data_slice__output,
                                                 dims_slice__v0[0],
                                                 (double*)data_slice__v0,
                                                 (double*)data_slice__v1,
                                                 NULL);

            if(!result && 0.0 == *(double*)data_slice__output)
            {

                // Poorly-defined problem. I indicate this with an all-zero
                // output, but I return true. This allows us to process
                // lots of data via broadcasting, without breaking ALL
                // the slices if one slice is broken
                memset((double*)data_slice__output, 0, 9*sizeof(double));
                return true;
            }
            return result;
        
}
#undef item__output
#undef ctype__output
#undef item__v0
#undef ctype__v0
#undef item__v1
#undef ctype__v1
#define ARGUMENTS(_) \
  _(v0) \
  _(v1)

#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___align_procrustes_vectors_R01_noweights__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___align_procrustes_vectors_R01_noweights__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___align_procrustes_vectors_R01_noweights(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
    ___align_procrustes_vectors_R01_noweights__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
    ___align_procrustes_vectors_R01_noweights__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___align_procrustes_vectors_R01_noweights__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_poseutils_npsp._align_procrustes_vectors_R01_noweights",
                                     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_v0[2] = {-1,3};
    const npy_intp PROTOTYPE_v1[2] = {-1,3};
    const npy_intp PROTOTYPE_output[2] = {3,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___align_procrustes_vectors_R01_noweights__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___align_procrustes_vectors_R01_noweights__ ## 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( ! ___align_procrustes_vectors_R01_noweights__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, "___align_procrustes_vectors_R01_noweights__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,
                                 "___align_procrustes_vectors_R01_noweights__slice failed!");
                goto done;
            }

        } while(__pywrap___align_procrustes_vectors_R01_noweights__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__v0
#undef CHECK_CONTIGUOUS__v0
#undef CHECK_CONTIGUOUS_AND_SETERROR__v0
#undef _CHECK_CONTIGUOUS__v1
#undef CHECK_CONTIGUOUS__v1
#undef CHECK_CONTIGUOUS_AND_SETERROR__v1

#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   _align_procrustes_points_Rt01_noweights

#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__v0(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__v0; i++)                               \
      if(dims_full__v0[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__v0; i--)                       \
      {                                                                 \
          if(strides_slice__v0[i+Ndims_slice__v0] != sizeof_element__v0*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'v0' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__v0[i+Ndims_slice__v0];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__v0()              _CHECK_CONTIGUOUS__v0(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__v0() _CHECK_CONTIGUOUS__v0(true)


#define _CHECK_CONTIGUOUS__v1(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__v1; i++)                               \
      if(dims_full__v1[i] == 0)                                     \
      {                                                                 \
          result     = true;                                            \
          have_dim_0 = true;                                            \
          break;                                                        \
      }                                                                 \
                                                                        \
  if(!have_dim_0)                                                       \
  {                                                                     \
      int Nelems_slice = 1;                                             \
      for(int i=-1; i>=-Ndims_slice__v1; i--)                       \
      {                                                                 \
          if(strides_slice__v1[i+Ndims_slice__v1] != sizeof_element__v1*Nelems_slice) \
          {                                                             \
              result = false;                                           \
              if(seterror)                                              \
                PyErr_Format(PyExc_RuntimeError,                        \
                             "Variable 'v1' must be contiguous in memory, and it isn't in (at least) dimension %d", i); \
              break;                                                    \
          }                                                             \
          Nelems_slice *= dims_slice__v1[i+Ndims_slice__v1];    \
      }                                                                 \
  }                                                                     \
  result;                                                               \
})
#define CHECK_CONTIGUOUS__v1()              _CHECK_CONTIGUOUS__v1(false)
#define CHECK_CONTIGUOUS_AND_SETERROR__v1() _CHECK_CONTIGUOUS__v1(true)


#define CHECK_CONTIGUOUS_ALL() CHECK_CONTIGUOUS__output() && CHECK_CONTIGUOUS__v0() && CHECK_CONTIGUOUS__v1()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__v0() && CHECK_CONTIGUOUS_AND_SETERROR__v1()

typedef struct {  } ___align_procrustes_points_Rt01_noweights__cookie_t;

static
bool ___align_procrustes_points_Rt01_noweights__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__v0 __attribute__((unused)),
  const npy_intp* dims_full__v0 __attribute__((unused)),
  const npy_intp* strides_full__v0 __attribute__((unused)),
  const int Ndims_slice__v0 __attribute__((unused)),
  const npy_intp* dims_slice__v0 __attribute__((unused)),
  const npy_intp* strides_slice__v0 __attribute__((unused)),
  npy_intp sizeof_element__v0 __attribute__((unused)),
  void* data__v0 __attribute__((unused)),
  const int Ndims_full__v1 __attribute__((unused)),
  const npy_intp* dims_full__v1 __attribute__((unused)),
  const npy_intp* strides_full__v1 __attribute__((unused)),
  const int Ndims_slice__v1 __attribute__((unused)),
  const npy_intp* dims_slice__v1 __attribute__((unused)),
  const npy_intp* strides_slice__v1 __attribute__((unused)),
  npy_intp sizeof_element__v1 __attribute__((unused)),
  void* data__v1 __attribute__((unused)),
  ___align_procrustes_points_Rt01_noweights__cookie_t* cookie __attribute__((unused)))
{

                    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__v0 npy_float64
#define item__v0(__ivar0,__ivar1) (*(ctype__v0*)(data_slice__v0 + (__ivar0)*strides_slice__v0[0]+ (__ivar1)*strides_slice__v0[1]))
#define ctype__v1 npy_float64
#define item__v1(__ivar0,__ivar1) (*(ctype__v1*)(data_slice__v1 + (__ivar0)*strides_slice__v1[0]+ (__ivar1)*strides_slice__v1[1]))

static
bool ___align_procrustes_points_Rt01_noweights__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__v0 __attribute__((unused)),
  const npy_intp* dims_full__v0 __attribute__((unused)),
  const npy_intp* strides_full__v0 __attribute__((unused)),
  const int Ndims_slice__v0 __attribute__((unused)),
  const npy_intp* dims_slice__v0 __attribute__((unused)),
  const npy_intp* strides_slice__v0 __attribute__((unused)),
  npy_intp sizeof_element__v0 __attribute__((unused)),
  void* data_slice__v0 __attribute__((unused)),
  const int Ndims_full__v1 __attribute__((unused)),
  const npy_intp* dims_full__v1 __attribute__((unused)),
  const npy_intp* strides_full__v1 __attribute__((unused)),
  const int Ndims_slice__v1 __attribute__((unused)),
  const npy_intp* dims_slice__v1 __attribute__((unused)),
  const npy_intp* strides_slice__v1 __attribute__((unused)),
  npy_intp sizeof_element__v1 __attribute__((unused)),
  void* data_slice__v1 __attribute__((unused)),
  ___align_procrustes_points_Rt01_noweights__cookie_t* cookie __attribute__((unused)))
{

            bool result =
            mrcal_align_procrustes_points_Rt01((double*)data_slice__output,
                                                 dims_slice__v0[0],
                                                 (double*)data_slice__v0,
                                                 (double*)data_slice__v1,
                                                 NULL);

            if(!result && 0.0 == *(double*)data_slice__output)
            {

                // Poorly-defined problem. I indicate this with an all-zero
                // output, but I return true. This allows us to process
                // lots of data via broadcasting, without breaking ALL
                // the slices if one slice is broken
                memset((double*)data_slice__output, 0, 12*sizeof(double));
                return true;
            }
            return result;
        
}
#undef item__output
#undef ctype__output
#undef item__v0
#undef ctype__v0
#undef item__v1
#undef ctype__v1
#define ARGUMENTS(_) \
  _(v0) \
  _(v1)

#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___align_procrustes_points_Rt01_noweights__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___align_procrustes_points_Rt01_noweights__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___align_procrustes_points_Rt01_noweights(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
    ___align_procrustes_points_Rt01_noweights__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
    ___align_procrustes_points_Rt01_noweights__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) ___align_procrustes_points_Rt01_noweights__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_poseutils_npsp._align_procrustes_points_Rt01_noweights",
                                     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_v0[2] = {-1,3};
    const npy_intp PROTOTYPE_v1[2] = {-1,3};
    const npy_intp PROTOTYPE_output[2] = {4,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___align_procrustes_points_Rt01_noweights__type_matches                \
                 (                                                      \
                             t0,t1,t2,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = ___align_procrustes_points_Rt01_noweights__ ## 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( ! ___align_procrustes_points_Rt01_noweights__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, "___align_procrustes_points_Rt01_noweights__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,
                                 "___align_procrustes_points_Rt01_noweights__slice failed!");
                goto done;
            }

        } while(__pywrap___align_procrustes_points_Rt01_noweights__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__v0
#undef CHECK_CONTIGUOUS__v0
#undef CHECK_CONTIGUOUS_AND_SETERROR__v0
#undef _CHECK_CONTIGUOUS__v1
#undef CHECK_CONTIGUOUS__v1
#undef CHECK_CONTIGUOUS_AND_SETERROR__v1

#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   R_aligned_to_vector

#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__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__v()
#define CHECK_CONTIGUOUS_AND_SETERROR_ALL() CHECK_CONTIGUOUS_AND_SETERROR__output() && CHECK_CONTIGUOUS_AND_SETERROR__v()

typedef struct {  } __R_aligned_to_vector__cookie_t;

static
bool __R_aligned_to_vector__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__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)),
  __R_aligned_to_vector__cookie_t* cookie __attribute__((unused)))
{

            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__v npy_float64
#define item__v(__ivar0) (*(ctype__v*)(data_slice__v + (__ivar0)*strides_slice__v[0]))

static
bool __R_aligned_to_vector__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__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)),
  __R_aligned_to_vector__cookie_t* cookie __attribute__((unused)))
{

    mrcal_R_aligned_to_vector((double*)data_slice__output,
                              (double*)data_slice__v);
    return true;

}
#undef item__output
#undef ctype__output
#undef item__v
#undef ctype__v
#define ARGUMENTS(_) \
  _(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__R_aligned_to_vector__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__R_aligned_to_vector__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__R_aligned_to_vector(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
    __R_aligned_to_vector__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
    __R_aligned_to_vector__cookie_t* cookie = &_cookie;

    typedef bool (slice_function_t)(OUTPUTS(SLICE_ARG) ARGUMENTS(SLICE_ARG) __R_aligned_to_vector__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_poseutils_npsp.R_aligned_to_vector",
                                     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_v[1] = {3};
    const npy_intp PROTOTYPE_output[2] = {3,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,0)
#define TYPESET_MATCHES(t0,t1, i)                   \
        else if( __pywrap__R_aligned_to_vector__type_matches                \
                 (                                                      \
                             t0,t1,                 \
                             ARGUMENTS(ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_CALL)                    \
                             OUTPUTS(  ARGLIST_SELECTED_TYPENUM_PTR_CALL) \
                             0 /* dummy; unused */                      \
                 )                                                      \
               )                                                        \
        {                                                               \
            /* matched */                                               \
            slice_function = __R_aligned_to_vector__ ## 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   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   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( ! __R_aligned_to_vector__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, "__R_aligned_to_vector__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,
                                 "__R_aligned_to_vector__slice failed!");
                goto done;
            }

        } while(__pywrap__R_aligned_to_vector__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__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_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_poseutils_npsp(void)
{
    Py_InitModule3("bindings_poseutils_npsp", methods, "Low-level routines to manipulate poses, transformations and points\n\nThis is the written-in-C Python extension module. Most of the time you want to\nuse the mrcal.poseutils wrapper module instead of this module directly. Any\nfunctions not prefixed with \"_\" are meant to be called directly, without the\nwrapper.\n\nAll functions are exported into the mrcal module. So you can call these via\nmrcal._poseutils.fff() or mrcal.fff(). The latter is preferred.\n\n");
    import_array();
}

#else

static struct PyModuleDef module_def =
    {
     PyModuleDef_HEAD_INIT,
     "bindings_poseutils_npsp", "Low-level routines to manipulate poses, transformations and points\n\nThis is the written-in-C Python extension module. Most of the time you want to\nuse the mrcal.poseutils wrapper module instead of this module directly. Any\nfunctions not prefixed with \"_\" are meant to be called directly, without the\nwrapper.\n\nAll functions are exported into the mrcal module. So you can call these via\nmrcal._poseutils.fff() or mrcal.fff(). The latter is preferred.\n\n",
     -1,
     methods
    };

PyMODINIT_FUNC PyInit_bindings_poseutils_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
