Generated by Cython 3.2.8

Yellow lines hint at Python interaction.
Click on a line that starts with a "+" to see the C code that Cython generated for it.

Raw output: _pbc.c

+001: ## ...........................................................................
  __pyx_t_4 = __Pyx_PyDict_NewPresized(0); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  if (PyDict_SetItem(__pyx_mstate_global->__pyx_d, __pyx_mstate_global->__pyx_n_u_test, __pyx_t_4) < (0)) __PYX_ERR(0, 1, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
 002: ##
 003: ## lemonade - analysis backend for PIMMS lattice trajectories
 004: ##
 005: ## Compiled kernels for the performance-critical trajectory operations: batched
 006: ## PBC unwrapping ("make whole") and per-frame grid painting. These replace the
 007: ## per-bead / per-frame pure-Python loops that dominated the original lemonade.
 008: ##
 009: ## ...........................................................................
 010: 
+011: import numpy as np
  __pyx_t_1 = __Pyx_Import(__pyx_mstate_global->__pyx_n_u_numpy, 0, 0, NULL, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 11, __pyx_L1_error)
  __pyx_t_4 = __pyx_t_1;
  __Pyx_GOTREF(__pyx_t_4);
  if (PyDict_SetItem(__pyx_mstate_global->__pyx_d, __pyx_mstate_global->__pyx_n_u_np, __pyx_t_4) < (0)) __PYX_ERR(0, 11, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
 012: cimport numpy as cnp
+013: cnp.import_array()
  __pyx_t_9 = __pyx_f_5numpy_import_array(); if (unlikely(__pyx_t_9 == ((int)-1))) __PYX_ERR(0, 13, __pyx_L1_error)
 014: cimport cython
 015: 
 016: 
+017: @cython.boundscheck(False)
/* Python wrapper */
static PyObject *__pyx_pw_5pimms_8lemonade_7kernels_4_pbc_1unwrap_chains(PyObject *__pyx_self, 
#if CYTHON_METH_FASTCALL
PyObject *const *__pyx_args, Py_ssize_t __pyx_nargs, PyObject *__pyx_kwds
#else
PyObject *__pyx_args, PyObject *__pyx_kwds
#endif
); /*proto*/
PyDoc_STRVAR(__pyx_doc_5pimms_8lemonade_7kernels_4_pbc_unwrap_chains, "\n    Make every chain whole across periodic boundaries, in every frame, at once.\n\n    For each frame and each chain (the atoms ``offsets[c] .. offsets[c+1]``), the\n    first bead is left in place and every subsequent bead is shifted by whole\n    multiples of the box so that consecutive beads never jump across a boundary -\n    i.e. the chain becomes spatially contiguous (coordinates may fall outside the\n    box). This is the batched, typed-C equivalent of\n    ``pimms.lattice_utils.make_chain_whole`` applied to the whole trajectory; it\n    replaces the per-call ``copy.deepcopy`` + triple-nested Python loop that made\n    single-image conversion the load-time bottleneck.\n\n    Parameters\n    ----------\n    positions : (n_frames, n_atoms, 3) int32\n        Wrapped, in-box integer lattice positions (z is 0 for 2D systems).\n    offsets : (n_chains + 1,) int64\n        CSR-style atom index boundaries, one contiguous block of atoms per chain.\n    dims : (3,) int64\n        Box size per axis (dims[2] is ignored / may be 1 for 2D).\n    n_dim : int\n        2 or 3.\n\n    Returns\n    -------\n    (n_frames, n_atoms, 3) int32\n        Unwrapped (\"whole\") positions, first bead of each chain unchanged.\n    ");
static PyMethodDef __pyx_mdef_5pimms_8lemonade_7kernels_4_pbc_1unwrap_chains = {"unwrap_chains", (PyCFunction)(void(*)(void))(__Pyx_PyCFunction_FastCallWithKeywords)__pyx_pw_5pimms_8lemonade_7kernels_4_pbc_1unwrap_chains, __Pyx_METH_FASTCALL|METH_KEYWORDS, __pyx_doc_5pimms_8lemonade_7kernels_4_pbc_unwrap_chains};
static PyObject *__pyx_pw_5pimms_8lemonade_7kernels_4_pbc_1unwrap_chains(PyObject *__pyx_self, 
#if CYTHON_METH_FASTCALL
PyObject *const *__pyx_args, Py_ssize_t __pyx_nargs, PyObject *__pyx_kwds
#else
PyObject *__pyx_args, PyObject *__pyx_kwds
#endif
) {
  __Pyx_memviewslice __pyx_v_positions = { 0, 0, { 0 }, { 0 }, { 0 } };
  __Pyx_memviewslice __pyx_v_offsets = { 0, 0, { 0 }, { 0 }, { 0 } };
  __Pyx_memviewslice __pyx_v_dims = { 0, 0, { 0 }, { 0 }, { 0 } };
  int __pyx_v_n_dim;
  #if !CYTHON_METH_FASTCALL
  CYTHON_UNUSED Py_ssize_t __pyx_nargs;
  #endif
  CYTHON_UNUSED PyObject *const *__pyx_kwvalues;
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("unwrap_chains (wrapper)", 0);
  #if !CYTHON_METH_FASTCALL
  #if CYTHON_ASSUME_SAFE_SIZE
  __pyx_nargs = PyTuple_GET_SIZE(__pyx_args);
  #else
  __pyx_nargs = PyTuple_Size(__pyx_args); if (unlikely(__pyx_nargs < 0)) return NULL;
  #endif
  #endif
  __pyx_kwvalues = __Pyx_KwValues_FASTCALL(__pyx_args, __pyx_nargs);
  {
    PyObject ** const __pyx_pyargnames[] = {&__pyx_mstate_global->__pyx_n_u_positions,&__pyx_mstate_global->__pyx_n_u_offsets,&__pyx_mstate_global->__pyx_n_u_dims,&__pyx_mstate_global->__pyx_n_u_n_dim,0};
  PyObject* values[4] = {0,0,0,0};
    const Py_ssize_t __pyx_kwds_len = (__pyx_kwds) ? __Pyx_NumKwargs_FASTCALL(__pyx_kwds) : 0;
    if (unlikely(__pyx_kwds_len < 0)) __PYX_ERR(0, 17, __pyx_L3_error)
    if (__pyx_kwds_len > 0) {
      switch (__pyx_nargs) {
        case  4:
        values[3] = __Pyx_ArgRef_FASTCALL(__pyx_args, 3);
        if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[3])) __PYX_ERR(0, 17, __pyx_L3_error)
        CYTHON_FALLTHROUGH;
        case  3:
        values[2] = __Pyx_ArgRef_FASTCALL(__pyx_args, 2);
        if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[2])) __PYX_ERR(0, 17, __pyx_L3_error)
        CYTHON_FALLTHROUGH;
        case  2:
        values[1] = __Pyx_ArgRef_FASTCALL(__pyx_args, 1);
        if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[1])) __PYX_ERR(0, 17, __pyx_L3_error)
        CYTHON_FALLTHROUGH;
        case  1:
        values[0] = __Pyx_ArgRef_FASTCALL(__pyx_args, 0);
        if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[0])) __PYX_ERR(0, 17, __pyx_L3_error)
        CYTHON_FALLTHROUGH;
        case  0: break;
        default: goto __pyx_L5_argtuple_error;
      }
      const Py_ssize_t kwd_pos_args = __pyx_nargs;
      if (__Pyx_ParseKeywords(__pyx_kwds, __pyx_kwvalues, __pyx_pyargnames, 0, values, kwd_pos_args, __pyx_kwds_len, "unwrap_chains", 0) < (0)) __PYX_ERR(0, 17, __pyx_L3_error)
      for (Py_ssize_t i = __pyx_nargs; i < 4; i++) {
        if (unlikely(!values[i])) { __Pyx_RaiseArgtupleInvalid("unwrap_chains", 1, 4, 4, i); __PYX_ERR(0, 17, __pyx_L3_error) }
      }
    } else if (unlikely(__pyx_nargs != 4)) {
      goto __pyx_L5_argtuple_error;
    } else {
      values[0] = __Pyx_ArgRef_FASTCALL(__pyx_args, 0);
      if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[0])) __PYX_ERR(0, 17, __pyx_L3_error)
      values[1] = __Pyx_ArgRef_FASTCALL(__pyx_args, 1);
      if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[1])) __PYX_ERR(0, 17, __pyx_L3_error)
      values[2] = __Pyx_ArgRef_FASTCALL(__pyx_args, 2);
      if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[2])) __PYX_ERR(0, 17, __pyx_L3_error)
      values[3] = __Pyx_ArgRef_FASTCALL(__pyx_args, 3);
      if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[3])) __PYX_ERR(0, 17, __pyx_L3_error)
    }
    __pyx_v_positions = __Pyx_PyObject_to_MemoryviewSlice_d_d_dc_nn___pyx_t_5numpy_int32_t(values[0], PyBUF_WRITABLE); if (unlikely(!__pyx_v_positions.memview)) __PYX_ERR(0, 19, __pyx_L3_error)
    __pyx_v_offsets = __Pyx_PyObject_to_MemoryviewSlice_dc_nn___pyx_t_5numpy_int64_t(values[1], PyBUF_WRITABLE); if (unlikely(!__pyx_v_offsets.memview)) __PYX_ERR(0, 20, __pyx_L3_error)
    __pyx_v_dims = __Pyx_PyObject_to_MemoryviewSlice_dc_nn___pyx_t_5numpy_int64_t(values[2], PyBUF_WRITABLE); if (unlikely(!__pyx_v_dims.memview)) __PYX_ERR(0, 21, __pyx_L3_error)
    __pyx_v_n_dim = __Pyx_PyLong_As_int(values[3]); if (unlikely((__pyx_v_n_dim == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 22, __pyx_L3_error)
  }
  goto __pyx_L6_skip;
  __pyx_L5_argtuple_error:;
  __Pyx_RaiseArgtupleInvalid("unwrap_chains", 1, 4, 4, __pyx_nargs); __PYX_ERR(0, 17, __pyx_L3_error)
  __pyx_L6_skip:;
  goto __pyx_L4_argument_unpacking_done;
  __pyx_L3_error:;
  for (Py_ssize_t __pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
    Py_XDECREF(values[__pyx_temp]);
  }
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_positions, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_offsets, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_dims, 1);
  __Pyx_AddTraceback("pimms.lemonade.kernels._pbc.unwrap_chains", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __Pyx_RefNannyFinishContext();
  return NULL;
  __pyx_L4_argument_unpacking_done:;
  __pyx_r = __pyx_pf_5pimms_8lemonade_7kernels_4_pbc_unwrap_chains(__pyx_self, __pyx_v_positions, __pyx_v_offsets, __pyx_v_dims, __pyx_v_n_dim);
  int __pyx_lineno = 0;
  const char *__pyx_filename = NULL;
  int __pyx_clineno = 0;

  /* function exit code */
  for (Py_ssize_t __pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
    Py_XDECREF(values[__pyx_temp]);
  }
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_positions, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_offsets, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_dims, 1);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_5pimms_8lemonade_7kernels_4_pbc_unwrap_chains(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_memviewslice __pyx_v_positions, __Pyx_memviewslice __pyx_v_offsets, __Pyx_memviewslice __pyx_v_dims, int __pyx_v_n_dim) {
  Py_ssize_t __pyx_v_n_frames;
  CYTHON_UNUSED Py_ssize_t __pyx_v_n_atoms;
  Py_ssize_t __pyx_v_n_chains;
  Py_ssize_t __pyx_v_f;
  Py_ssize_t __pyx_v_c;
  Py_ssize_t __pyx_v_a;
  Py_ssize_t __pyx_v_a0;
  Py_ssize_t __pyx_v_a1;
  int __pyx_v_d;
  long __pyx_v_dim;
  long __pyx_v_cur;
  long __pyx_v_v;
  long __pyx_v_guard;
  PyObject *__pyx_v_out_np = NULL;
  __Pyx_memviewslice __pyx_v_out = { 0, 0, { 0 }, { 0 }, { 0 } };
  PyObject *__pyx_r = NULL;
/* … */
  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_1);
  __Pyx_XDECREF(__pyx_t_2);
  __Pyx_XDECREF(__pyx_t_3);
  __Pyx_XDECREF(__pyx_t_4);
  __Pyx_XDECREF(__pyx_t_5);
  __Pyx_XDECREF(__pyx_t_6);
  __PYX_XCLEAR_MEMVIEW(&__pyx_t_8, 1);
  __Pyx_AddTraceback("pimms.lemonade.kernels._pbc.unwrap_chains", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = NULL;
  __pyx_L0:;
  __Pyx_XDECREF(__pyx_v_out_np);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_out, 1);
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
/* … */
  __pyx_t_4 = __Pyx_CyFunction_New(&__pyx_mdef_5pimms_8lemonade_7kernels_4_pbc_1unwrap_chains, 0, __pyx_mstate_global->__pyx_n_u_unwrap_chains, NULL, __pyx_mstate_global->__pyx_n_u_pimms_lemonade_kernels__pbc, __pyx_mstate_global->__pyx_d, ((PyObject *)__pyx_mstate_global->__pyx_codeobj_tab[0])); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 17, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  #if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030E0000
  PyUnstable_Object_EnableDeferredRefcount(__pyx_t_4);
  #endif
  if (PyDict_SetItem(__pyx_mstate_global->__pyx_d, __pyx_mstate_global->__pyx_n_u_unwrap_chains, __pyx_t_4) < (0)) __PYX_ERR(0, 17, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
 018: @cython.wraparound(False)
 019: def unwrap_chains(cnp.int32_t[:, :, ::1] positions,
 020:                   cnp.int64_t[::1] offsets,
 021:                   cnp.int64_t[::1] dims,
 022:                   int n_dim):
 023:     """
 024:     Make every chain whole across periodic boundaries, in every frame, at once.
 025: 
 026:     For each frame and each chain (the atoms ``offsets[c] .. offsets[c+1]``), the
 027:     first bead is left in place and every subsequent bead is shifted by whole
 028:     multiples of the box so that consecutive beads never jump across a boundary -
 029:     i.e. the chain becomes spatially contiguous (coordinates may fall outside the
 030:     box). This is the batched, typed-C equivalent of
 031:     ``pimms.lattice_utils.make_chain_whole`` applied to the whole trajectory; it
 032:     replaces the per-call ``copy.deepcopy`` + triple-nested Python loop that made
 033:     single-image conversion the load-time bottleneck.
 034: 
 035:     Parameters
 036:     ----------
 037:     positions : (n_frames, n_atoms, 3) int32
 038:         Wrapped, in-box integer lattice positions (z is 0 for 2D systems).
 039:     offsets : (n_chains + 1,) int64
 040:         CSR-style atom index boundaries, one contiguous block of atoms per chain.
 041:     dims : (3,) int64
 042:         Box size per axis (dims[2] is ignored / may be 1 for 2D).
 043:     n_dim : int
 044:         2 or 3.
 045: 
 046:     Returns
 047:     -------
 048:     (n_frames, n_atoms, 3) int32
 049:         Unwrapped ("whole") positions, first bead of each chain unchanged.
 050:     """
+051:     cdef Py_ssize_t n_frames = positions.shape[0]
  __pyx_v_n_frames = (__pyx_v_positions.shape[0]);
+052:     cdef Py_ssize_t n_atoms = positions.shape[1]
  __pyx_v_n_atoms = (__pyx_v_positions.shape[1]);
+053:     cdef Py_ssize_t n_chains = offsets.shape[0] - 1
  __pyx_v_n_chains = ((__pyx_v_offsets.shape[0]) - 1);
 054:     cdef Py_ssize_t f, c, a, a0, a1
 055:     cdef int d
 056:     cdef long dim, cur, v, guard
 057: 
+058:     out_np = np.asarray(positions).copy()
  __pyx_t_4 = NULL;
  __Pyx_GetModuleGlobalName(__pyx_t_5, __pyx_mstate_global->__pyx_n_u_np); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 58, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_5);
  __pyx_t_6 = __Pyx_PyObject_GetAttrStr(__pyx_t_5, __pyx_mstate_global->__pyx_n_u_asarray); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 58, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_6);
  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
  __pyx_t_5 = __pyx_memoryview_fromslice(__pyx_v_positions, 3, (PyObject *(*)(char *)) __pyx_memview_get_nn___pyx_t_5numpy_int32_t, (int (*)(char *, PyObject *)) __pyx_memview_set_nn___pyx_t_5numpy_int32_t, 0);; if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 58, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_5);
  __pyx_t_7 = 1;
  #if CYTHON_UNPACK_METHODS
  if (unlikely(PyMethod_Check(__pyx_t_6))) {
    __pyx_t_4 = PyMethod_GET_SELF(__pyx_t_6);
    assert(__pyx_t_4);
    PyObject* __pyx__function = PyMethod_GET_FUNCTION(__pyx_t_6);
    __Pyx_INCREF(__pyx_t_4);
    __Pyx_INCREF(__pyx__function);
    __Pyx_DECREF_SET(__pyx_t_6, __pyx__function);
    __pyx_t_7 = 0;
  }
  #endif
  {
    PyObject *__pyx_callargs[2] = {__pyx_t_4, __pyx_t_5};
    __pyx_t_3 = __Pyx_PyObject_FastCall((PyObject*)__pyx_t_6, __pyx_callargs+__pyx_t_7, (2-__pyx_t_7) | (__pyx_t_7*__Pyx_PY_VECTORCALL_ARGUMENTS_OFFSET));
    __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0;
    __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
    __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
    if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 58, __pyx_L1_error)
    __Pyx_GOTREF(__pyx_t_3);
  }
  __pyx_t_2 = __pyx_t_3;
  __Pyx_INCREF(__pyx_t_2);
  __pyx_t_7 = 0;
  {
    PyObject *__pyx_callargs[2] = {__pyx_t_2, NULL};
    __pyx_t_1 = __Pyx_PyObject_FastCallMethod((PyObject*)__pyx_mstate_global->__pyx_n_u_copy, __pyx_callargs+__pyx_t_7, (1-__pyx_t_7) | (1*__Pyx_PY_VECTORCALL_ARGUMENTS_OFFSET));
    __Pyx_XDECREF(__pyx_t_2); __pyx_t_2 = 0;
    __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0;
    if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 58, __pyx_L1_error)
    __Pyx_GOTREF(__pyx_t_1);
  }
  __pyx_v_out_np = __pyx_t_1;
  __pyx_t_1 = 0;
+059:     cdef cnp.int32_t[:, :, ::1] out = out_np
  __pyx_t_8 = __Pyx_PyObject_to_MemoryviewSlice_d_d_dc_nn___pyx_t_5numpy_int32_t(__pyx_v_out_np, PyBUF_WRITABLE); if (unlikely(!__pyx_t_8.memview)) __PYX_ERR(0, 59, __pyx_L1_error)
  __pyx_v_out = __pyx_t_8;
  __pyx_t_8.memview = NULL;
  __pyx_t_8.data = NULL;
 060: 
+061:     for f in range(n_frames):
  __pyx_t_9 = __pyx_v_n_frames;
  __pyx_t_10 = __pyx_t_9;
  for (__pyx_t_11 = 0; __pyx_t_11 < __pyx_t_10; __pyx_t_11+=1) {
    __pyx_v_f = __pyx_t_11;
+062:         for c in range(n_chains):
    __pyx_t_12 = __pyx_v_n_chains;
    __pyx_t_13 = __pyx_t_12;
    for (__pyx_t_14 = 0; __pyx_t_14 < __pyx_t_13; __pyx_t_14+=1) {
      __pyx_v_c = __pyx_t_14;
+063:             a0 = offsets[c]
      __pyx_t_15 = __pyx_v_c;
      __pyx_v_a0 = (*((__pyx_t_5numpy_int64_t *) ( /* dim=0 */ ((char *) (((__pyx_t_5numpy_int64_t *) __pyx_v_offsets.data) + __pyx_t_15)) )));
+064:             a1 = offsets[c + 1]
      __pyx_t_15 = (__pyx_v_c + 1);
      __pyx_v_a1 = (*((__pyx_t_5numpy_int64_t *) ( /* dim=0 */ ((char *) (((__pyx_t_5numpy_int64_t *) __pyx_v_offsets.data) + __pyx_t_15)) )));
+065:             for d in range(n_dim):
      __pyx_t_16 = __pyx_v_n_dim;
      __pyx_t_17 = __pyx_t_16;
      for (__pyx_t_18 = 0; __pyx_t_18 < __pyx_t_17; __pyx_t_18+=1) {
        __pyx_v_d = __pyx_t_18;
+066:                 dim = dims[d]
        __pyx_t_15 = __pyx_v_d;
        __pyx_v_dim = (*((__pyx_t_5numpy_int64_t *) ( /* dim=0 */ ((char *) (((__pyx_t_5numpy_int64_t *) __pyx_v_dims.data) + __pyx_t_15)) )));
+067:                 cur = positions[f, a0, d]           # anchor: first bead unchanged
        __pyx_t_15 = __pyx_v_f;
        __pyx_t_19 = __pyx_v_a0;
        __pyx_t_20 = __pyx_v_d;
        __pyx_v_cur = (*((__pyx_t_5numpy_int32_t *) ( /* dim=2 */ ((char *) (((__pyx_t_5numpy_int32_t *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_positions.data + __pyx_t_15 * __pyx_v_positions.strides[0]) ) + __pyx_t_19 * __pyx_v_positions.strides[1]) )) + __pyx_t_20)) )));
+068:                 for a in range(a0 + 1, a1):
        __pyx_t_21 = __pyx_v_a1;
        __pyx_t_22 = __pyx_t_21;
        for (__pyx_t_23 = (__pyx_v_a0 + 1); __pyx_t_23 < __pyx_t_22; __pyx_t_23+=1) {
          __pyx_v_a = __pyx_t_23;
+069:                     v = positions[f, a, d]
          __pyx_t_20 = __pyx_v_f;
          __pyx_t_19 = __pyx_v_a;
          __pyx_t_15 = __pyx_v_d;
          __pyx_v_v = (*((__pyx_t_5numpy_int32_t *) ( /* dim=2 */ ((char *) (((__pyx_t_5numpy_int32_t *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_positions.data + __pyx_t_20 * __pyx_v_positions.strides[0]) ) + __pyx_t_19 * __pyx_v_positions.strides[1]) )) + __pyx_t_15)) )));
+070:                     if cur - v > 1:
          __pyx_t_24 = ((__pyx_v_cur - __pyx_v_v) > 1);
          if (__pyx_t_24) {
/* … */
            goto __pyx_L11;
          }
 071:                         # neighbour sits across the +boundary; walk it up
+072:                         v += dim
            __pyx_v_v = (__pyx_v_v + __pyx_v_dim);
+073:                         guard = 0
            __pyx_v_guard = 0;
+074:                         while v - cur > 1 or cur - v > 1:
            while (1) {
              __pyx_t_25 = ((__pyx_v_v - __pyx_v_cur) > 1);
              if (!__pyx_t_25) {
              } else {
                __pyx_t_24 = __pyx_t_25;
                goto __pyx_L14_bool_binop_done;
              }
              __pyx_t_25 = ((__pyx_v_cur - __pyx_v_v) > 1);
              __pyx_t_24 = __pyx_t_25;
              __pyx_L14_bool_binop_done:;
              if (!__pyx_t_24) break;
+075:                             v += dim
              __pyx_v_v = (__pyx_v_v + __pyx_v_dim);
+076:                             guard += 1
              __pyx_v_guard = (__pyx_v_guard + 1);
+077:                             if guard > 100000:
              __pyx_t_24 = (__pyx_v_guard > 0x186A0);
              if (unlikely(__pyx_t_24)) {
/* … */
              }
            }
+078:                                 raise ValueError(
                __pyx_t_3 = NULL;
                __pyx_t_7 = 1;
                {
                  PyObject *__pyx_callargs[2] = {__pyx_t_3, __pyx_mstate_global->__pyx_kp_u_unwrap_chains_unresolvable_bond};
                  __pyx_t_1 = __Pyx_PyObject_FastCall((PyObject*)(((PyTypeObject*)PyExc_ValueError)), __pyx_callargs+__pyx_t_7, (2-__pyx_t_7) | (__pyx_t_7*__Pyx_PY_VECTORCALL_ARGUMENTS_OFFSET));
                  __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0;
                  if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 78, __pyx_L1_error)
                  __Pyx_GOTREF(__pyx_t_1);
                }
                __Pyx_Raise(__pyx_t_1, 0, 0, 0);
                __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
                __PYX_ERR(0, 78, __pyx_L1_error)
 079:                                     "unwrap_chains: unresolvable bond (impossible/non-unit step in chain)")
+080:                     elif cur - v < -1:
          __pyx_t_24 = ((__pyx_v_cur - __pyx_v_v) < -1L);
          if (__pyx_t_24) {
/* … */
          }
          __pyx_L11:;
 081:                         # neighbour sits across the -boundary; walk it down
+082:                         v -= dim
            __pyx_v_v = (__pyx_v_v - __pyx_v_dim);
+083:                         guard = 0
            __pyx_v_guard = 0;
+084:                         while v - cur > 1 or cur - v > 1:
            while (1) {
              __pyx_t_25 = ((__pyx_v_v - __pyx_v_cur) > 1);
              if (!__pyx_t_25) {
              } else {
                __pyx_t_24 = __pyx_t_25;
                goto __pyx_L19_bool_binop_done;
              }
              __pyx_t_25 = ((__pyx_v_cur - __pyx_v_v) > 1);
              __pyx_t_24 = __pyx_t_25;
              __pyx_L19_bool_binop_done:;
              if (!__pyx_t_24) break;
+085:                             v -= dim
              __pyx_v_v = (__pyx_v_v - __pyx_v_dim);
+086:                             guard += 1
              __pyx_v_guard = (__pyx_v_guard + 1);
+087:                             if guard > 100000:
              __pyx_t_24 = (__pyx_v_guard > 0x186A0);
              if (unlikely(__pyx_t_24)) {
/* … */
              }
            }
+088:                                 raise ValueError(
                __pyx_t_3 = NULL;
                __pyx_t_7 = 1;
                {
                  PyObject *__pyx_callargs[2] = {__pyx_t_3, __pyx_mstate_global->__pyx_kp_u_unwrap_chains_unresolvable_bond};
                  __pyx_t_1 = __Pyx_PyObject_FastCall((PyObject*)(((PyTypeObject*)PyExc_ValueError)), __pyx_callargs+__pyx_t_7, (2-__pyx_t_7) | (__pyx_t_7*__Pyx_PY_VECTORCALL_ARGUMENTS_OFFSET));
                  __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0;
                  if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 88, __pyx_L1_error)
                  __Pyx_GOTREF(__pyx_t_1);
                }
                __Pyx_Raise(__pyx_t_1, 0, 0, 0);
                __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
                __PYX_ERR(0, 88, __pyx_L1_error)
 089:                                     "unwrap_chains: unresolvable bond (impossible/non-unit step in chain)")
+090:                     out[f, a, d] = v
          __pyx_t_15 = __pyx_v_f;
          __pyx_t_19 = __pyx_v_a;
          __pyx_t_20 = __pyx_v_d;
          *((__pyx_t_5numpy_int32_t *) ( /* dim=2 */ ((char *) (((__pyx_t_5numpy_int32_t *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_out.data + __pyx_t_15 * __pyx_v_out.strides[0]) ) + __pyx_t_19 * __pyx_v_out.strides[1]) )) + __pyx_t_20)) )) = __pyx_v_v;
+091:                     cur = v
          __pyx_v_cur = __pyx_v_v;
        }
      }
    }
  }
+092:     return out_np
  __Pyx_XDECREF(__pyx_r);
  __Pyx_INCREF(__pyx_v_out_np);
  __pyx_r = __pyx_v_out_np;
  goto __pyx_L0;
 093: 
 094: 
+095: @cython.boundscheck(False)
/* Python wrapper */
static PyObject *__pyx_pw_5pimms_8lemonade_7kernels_4_pbc_3paint_frame_grid_3d(PyObject *__pyx_self, 
#if CYTHON_METH_FASTCALL
PyObject *const *__pyx_args, Py_ssize_t __pyx_nargs, PyObject *__pyx_kwds
#else
PyObject *__pyx_args, PyObject *__pyx_kwds
#endif
); /*proto*/
PyDoc_STRVAR(__pyx_doc_5pimms_8lemonade_7kernels_4_pbc_2paint_frame_grid_3d, "\n    Paint one frame's beads onto a 3D grid: ``grid[x, y, z] = chainID`` for every\n    occupied site (0 = empty). ``grid`` must be pre-zeroed and sized to the box.\n    Positions must already be wrapped into the box.\n    ");
static PyMethodDef __pyx_mdef_5pimms_8lemonade_7kernels_4_pbc_3paint_frame_grid_3d = {"paint_frame_grid_3d", (PyCFunction)(void(*)(void))(__Pyx_PyCFunction_FastCallWithKeywords)__pyx_pw_5pimms_8lemonade_7kernels_4_pbc_3paint_frame_grid_3d, __Pyx_METH_FASTCALL|METH_KEYWORDS, __pyx_doc_5pimms_8lemonade_7kernels_4_pbc_2paint_frame_grid_3d};
static PyObject *__pyx_pw_5pimms_8lemonade_7kernels_4_pbc_3paint_frame_grid_3d(PyObject *__pyx_self, 
#if CYTHON_METH_FASTCALL
PyObject *const *__pyx_args, Py_ssize_t __pyx_nargs, PyObject *__pyx_kwds
#else
PyObject *__pyx_args, PyObject *__pyx_kwds
#endif
) {
  __Pyx_memviewslice __pyx_v_frame_positions = { 0, 0, { 0 }, { 0 }, { 0 } };
  __Pyx_memviewslice __pyx_v_atom_chainid = { 0, 0, { 0 }, { 0 }, { 0 } };
  __Pyx_memviewslice __pyx_v_grid = { 0, 0, { 0 }, { 0 }, { 0 } };
  #if !CYTHON_METH_FASTCALL
  CYTHON_UNUSED Py_ssize_t __pyx_nargs;
  #endif
  CYTHON_UNUSED PyObject *const *__pyx_kwvalues;
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("paint_frame_grid_3d (wrapper)", 0);
  #if !CYTHON_METH_FASTCALL
  #if CYTHON_ASSUME_SAFE_SIZE
  __pyx_nargs = PyTuple_GET_SIZE(__pyx_args);
  #else
  __pyx_nargs = PyTuple_Size(__pyx_args); if (unlikely(__pyx_nargs < 0)) return NULL;
  #endif
  #endif
  __pyx_kwvalues = __Pyx_KwValues_FASTCALL(__pyx_args, __pyx_nargs);
  {
    PyObject ** const __pyx_pyargnames[] = {&__pyx_mstate_global->__pyx_n_u_frame_positions,&__pyx_mstate_global->__pyx_n_u_atom_chainid,&__pyx_mstate_global->__pyx_n_u_grid,0};
  PyObject* values[3] = {0,0,0};
    const Py_ssize_t __pyx_kwds_len = (__pyx_kwds) ? __Pyx_NumKwargs_FASTCALL(__pyx_kwds) : 0;
    if (unlikely(__pyx_kwds_len < 0)) __PYX_ERR(0, 95, __pyx_L3_error)
    if (__pyx_kwds_len > 0) {
      switch (__pyx_nargs) {
        case  3:
        values[2] = __Pyx_ArgRef_FASTCALL(__pyx_args, 2);
        if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[2])) __PYX_ERR(0, 95, __pyx_L3_error)
        CYTHON_FALLTHROUGH;
        case  2:
        values[1] = __Pyx_ArgRef_FASTCALL(__pyx_args, 1);
        if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[1])) __PYX_ERR(0, 95, __pyx_L3_error)
        CYTHON_FALLTHROUGH;
        case  1:
        values[0] = __Pyx_ArgRef_FASTCALL(__pyx_args, 0);
        if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[0])) __PYX_ERR(0, 95, __pyx_L3_error)
        CYTHON_FALLTHROUGH;
        case  0: break;
        default: goto __pyx_L5_argtuple_error;
      }
      const Py_ssize_t kwd_pos_args = __pyx_nargs;
      if (__Pyx_ParseKeywords(__pyx_kwds, __pyx_kwvalues, __pyx_pyargnames, 0, values, kwd_pos_args, __pyx_kwds_len, "paint_frame_grid_3d", 0) < (0)) __PYX_ERR(0, 95, __pyx_L3_error)
      for (Py_ssize_t i = __pyx_nargs; i < 3; i++) {
        if (unlikely(!values[i])) { __Pyx_RaiseArgtupleInvalid("paint_frame_grid_3d", 1, 3, 3, i); __PYX_ERR(0, 95, __pyx_L3_error) }
      }
    } else if (unlikely(__pyx_nargs != 3)) {
      goto __pyx_L5_argtuple_error;
    } else {
      values[0] = __Pyx_ArgRef_FASTCALL(__pyx_args, 0);
      if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[0])) __PYX_ERR(0, 95, __pyx_L3_error)
      values[1] = __Pyx_ArgRef_FASTCALL(__pyx_args, 1);
      if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[1])) __PYX_ERR(0, 95, __pyx_L3_error)
      values[2] = __Pyx_ArgRef_FASTCALL(__pyx_args, 2);
      if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[2])) __PYX_ERR(0, 95, __pyx_L3_error)
    }
    __pyx_v_frame_positions = __Pyx_PyObject_to_MemoryviewSlice_d_dc_nn___pyx_t_5numpy_int32_t(values[0], PyBUF_WRITABLE); if (unlikely(!__pyx_v_frame_positions.memview)) __PYX_ERR(0, 97, __pyx_L3_error)
    __pyx_v_atom_chainid = __Pyx_PyObject_to_MemoryviewSlice_dc_nn___pyx_t_5numpy_int32_t(values[1], PyBUF_WRITABLE); if (unlikely(!__pyx_v_atom_chainid.memview)) __PYX_ERR(0, 98, __pyx_L3_error)
    __pyx_v_grid = __Pyx_PyObject_to_MemoryviewSlice_d_d_dc_nn___pyx_t_5numpy_int32_t(values[2], PyBUF_WRITABLE); if (unlikely(!__pyx_v_grid.memview)) __PYX_ERR(0, 99, __pyx_L3_error)
  }
  goto __pyx_L6_skip;
  __pyx_L5_argtuple_error:;
  __Pyx_RaiseArgtupleInvalid("paint_frame_grid_3d", 1, 3, 3, __pyx_nargs); __PYX_ERR(0, 95, __pyx_L3_error)
  __pyx_L6_skip:;
  goto __pyx_L4_argument_unpacking_done;
  __pyx_L3_error:;
  for (Py_ssize_t __pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
    Py_XDECREF(values[__pyx_temp]);
  }
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_frame_positions, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_atom_chainid, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_grid, 1);
  __Pyx_AddTraceback("pimms.lemonade.kernels._pbc.paint_frame_grid_3d", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __Pyx_RefNannyFinishContext();
  return NULL;
  __pyx_L4_argument_unpacking_done:;
  __pyx_r = __pyx_pf_5pimms_8lemonade_7kernels_4_pbc_2paint_frame_grid_3d(__pyx_self, __pyx_v_frame_positions, __pyx_v_atom_chainid, __pyx_v_grid);
  int __pyx_lineno = 0;
  const char *__pyx_filename = NULL;
  int __pyx_clineno = 0;

  /* function exit code */
  for (Py_ssize_t __pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
    Py_XDECREF(values[__pyx_temp]);
  }
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_frame_positions, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_atom_chainid, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_grid, 1);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_5pimms_8lemonade_7kernels_4_pbc_2paint_frame_grid_3d(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_memviewslice __pyx_v_frame_positions, __Pyx_memviewslice __pyx_v_atom_chainid, __Pyx_memviewslice __pyx_v_grid) {
  Py_ssize_t __pyx_v_n;
  Py_ssize_t __pyx_v_i;
  PyObject *__pyx_r = NULL;
/* … */
  /* function exit code */
  __pyx_r = Py_None; __Pyx_INCREF(Py_None);
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
/* … */
  __pyx_t_4 = __Pyx_CyFunction_New(&__pyx_mdef_5pimms_8lemonade_7kernels_4_pbc_3paint_frame_grid_3d, 0, __pyx_mstate_global->__pyx_n_u_paint_frame_grid_3d, NULL, __pyx_mstate_global->__pyx_n_u_pimms_lemonade_kernels__pbc, __pyx_mstate_global->__pyx_d, ((PyObject *)__pyx_mstate_global->__pyx_codeobj_tab[1])); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 95, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  #if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030E0000
  PyUnstable_Object_EnableDeferredRefcount(__pyx_t_4);
  #endif
  if (PyDict_SetItem(__pyx_mstate_global->__pyx_d, __pyx_mstate_global->__pyx_n_u_paint_frame_grid_3d, __pyx_t_4) < (0)) __PYX_ERR(0, 95, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
 096: @cython.wraparound(False)
 097: def paint_frame_grid_3d(cnp.int32_t[:, ::1] frame_positions,
 098:                         cnp.int32_t[::1] atom_chainid,
 099:                         cnp.int32_t[:, :, ::1] grid):
 100:     """
 101:     Paint one frame's beads onto a 3D grid: ``grid[x, y, z] = chainID`` for every
 102:     occupied site (0 = empty). ``grid`` must be pre-zeroed and sized to the box.
 103:     Positions must already be wrapped into the box.
 104:     """
+105:     cdef Py_ssize_t n = frame_positions.shape[0]
  __pyx_v_n = (__pyx_v_frame_positions.shape[0]);
 106:     cdef Py_ssize_t i
+107:     for i in range(n):
  __pyx_t_1 = __pyx_v_n;
  __pyx_t_2 = __pyx_t_1;
  for (__pyx_t_3 = 0; __pyx_t_3 < __pyx_t_2; __pyx_t_3+=1) {
    __pyx_v_i = __pyx_t_3;
+108:         grid[frame_positions[i, 0], frame_positions[i, 1], frame_positions[i, 2]] = atom_chainid[i]
    __pyx_t_4 = __pyx_v_i;
    __pyx_t_5 = __pyx_v_i;
    __pyx_t_6 = 0;
    __pyx_t_7 = __pyx_v_i;
    __pyx_t_8 = 1;
    __pyx_t_9 = __pyx_v_i;
    __pyx_t_10 = 2;
    __pyx_t_11 = (*((__pyx_t_5numpy_int32_t *) ( /* dim=1 */ ((char *) (((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_frame_positions.data + __pyx_t_5 * __pyx_v_frame_positions.strides[0]) )) + __pyx_t_6)) )));
    __pyx_t_12 = (*((__pyx_t_5numpy_int32_t *) ( /* dim=1 */ ((char *) (((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_frame_positions.data + __pyx_t_7 * __pyx_v_frame_positions.strides[0]) )) + __pyx_t_8)) )));
    __pyx_t_13 = (*((__pyx_t_5numpy_int32_t *) ( /* dim=1 */ ((char *) (((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_frame_positions.data + __pyx_t_9 * __pyx_v_frame_positions.strides[0]) )) + __pyx_t_10)) )));
    *((__pyx_t_5numpy_int32_t *) ( /* dim=2 */ ((char *) (((__pyx_t_5numpy_int32_t *) ( /* dim=1 */ (( /* dim=0 */ (__pyx_v_grid.data + __pyx_t_11 * __pyx_v_grid.strides[0]) ) + __pyx_t_12 * __pyx_v_grid.strides[1]) )) + __pyx_t_13)) )) = (*((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ ((char *) (((__pyx_t_5numpy_int32_t *) __pyx_v_atom_chainid.data) + __pyx_t_4)) )));
  }
 109: 
 110: 
+111: @cython.boundscheck(False)
/* Python wrapper */
static PyObject *__pyx_pw_5pimms_8lemonade_7kernels_4_pbc_5paint_frame_grid_2d(PyObject *__pyx_self, 
#if CYTHON_METH_FASTCALL
PyObject *const *__pyx_args, Py_ssize_t __pyx_nargs, PyObject *__pyx_kwds
#else
PyObject *__pyx_args, PyObject *__pyx_kwds
#endif
); /*proto*/
PyDoc_STRVAR(__pyx_doc_5pimms_8lemonade_7kernels_4_pbc_4paint_frame_grid_2d, "2D counterpart of :func:`paint_frame_grid_3d` (ignores the z column).");
static PyMethodDef __pyx_mdef_5pimms_8lemonade_7kernels_4_pbc_5paint_frame_grid_2d = {"paint_frame_grid_2d", (PyCFunction)(void(*)(void))(__Pyx_PyCFunction_FastCallWithKeywords)__pyx_pw_5pimms_8lemonade_7kernels_4_pbc_5paint_frame_grid_2d, __Pyx_METH_FASTCALL|METH_KEYWORDS, __pyx_doc_5pimms_8lemonade_7kernels_4_pbc_4paint_frame_grid_2d};
static PyObject *__pyx_pw_5pimms_8lemonade_7kernels_4_pbc_5paint_frame_grid_2d(PyObject *__pyx_self, 
#if CYTHON_METH_FASTCALL
PyObject *const *__pyx_args, Py_ssize_t __pyx_nargs, PyObject *__pyx_kwds
#else
PyObject *__pyx_args, PyObject *__pyx_kwds
#endif
) {
  __Pyx_memviewslice __pyx_v_frame_positions = { 0, 0, { 0 }, { 0 }, { 0 } };
  __Pyx_memviewslice __pyx_v_atom_chainid = { 0, 0, { 0 }, { 0 }, { 0 } };
  __Pyx_memviewslice __pyx_v_grid = { 0, 0, { 0 }, { 0 }, { 0 } };
  #if !CYTHON_METH_FASTCALL
  CYTHON_UNUSED Py_ssize_t __pyx_nargs;
  #endif
  CYTHON_UNUSED PyObject *const *__pyx_kwvalues;
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("paint_frame_grid_2d (wrapper)", 0);
  #if !CYTHON_METH_FASTCALL
  #if CYTHON_ASSUME_SAFE_SIZE
  __pyx_nargs = PyTuple_GET_SIZE(__pyx_args);
  #else
  __pyx_nargs = PyTuple_Size(__pyx_args); if (unlikely(__pyx_nargs < 0)) return NULL;
  #endif
  #endif
  __pyx_kwvalues = __Pyx_KwValues_FASTCALL(__pyx_args, __pyx_nargs);
  {
    PyObject ** const __pyx_pyargnames[] = {&__pyx_mstate_global->__pyx_n_u_frame_positions,&__pyx_mstate_global->__pyx_n_u_atom_chainid,&__pyx_mstate_global->__pyx_n_u_grid,0};
  PyObject* values[3] = {0,0,0};
    const Py_ssize_t __pyx_kwds_len = (__pyx_kwds) ? __Pyx_NumKwargs_FASTCALL(__pyx_kwds) : 0;
    if (unlikely(__pyx_kwds_len < 0)) __PYX_ERR(0, 111, __pyx_L3_error)
    if (__pyx_kwds_len > 0) {
      switch (__pyx_nargs) {
        case  3:
        values[2] = __Pyx_ArgRef_FASTCALL(__pyx_args, 2);
        if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[2])) __PYX_ERR(0, 111, __pyx_L3_error)
        CYTHON_FALLTHROUGH;
        case  2:
        values[1] = __Pyx_ArgRef_FASTCALL(__pyx_args, 1);
        if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[1])) __PYX_ERR(0, 111, __pyx_L3_error)
        CYTHON_FALLTHROUGH;
        case  1:
        values[0] = __Pyx_ArgRef_FASTCALL(__pyx_args, 0);
        if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[0])) __PYX_ERR(0, 111, __pyx_L3_error)
        CYTHON_FALLTHROUGH;
        case  0: break;
        default: goto __pyx_L5_argtuple_error;
      }
      const Py_ssize_t kwd_pos_args = __pyx_nargs;
      if (__Pyx_ParseKeywords(__pyx_kwds, __pyx_kwvalues, __pyx_pyargnames, 0, values, kwd_pos_args, __pyx_kwds_len, "paint_frame_grid_2d", 0) < (0)) __PYX_ERR(0, 111, __pyx_L3_error)
      for (Py_ssize_t i = __pyx_nargs; i < 3; i++) {
        if (unlikely(!values[i])) { __Pyx_RaiseArgtupleInvalid("paint_frame_grid_2d", 1, 3, 3, i); __PYX_ERR(0, 111, __pyx_L3_error) }
      }
    } else if (unlikely(__pyx_nargs != 3)) {
      goto __pyx_L5_argtuple_error;
    } else {
      values[0] = __Pyx_ArgRef_FASTCALL(__pyx_args, 0);
      if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[0])) __PYX_ERR(0, 111, __pyx_L3_error)
      values[1] = __Pyx_ArgRef_FASTCALL(__pyx_args, 1);
      if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[1])) __PYX_ERR(0, 111, __pyx_L3_error)
      values[2] = __Pyx_ArgRef_FASTCALL(__pyx_args, 2);
      if (!CYTHON_ASSUME_SAFE_MACROS && unlikely(!values[2])) __PYX_ERR(0, 111, __pyx_L3_error)
    }
    __pyx_v_frame_positions = __Pyx_PyObject_to_MemoryviewSlice_d_dc_nn___pyx_t_5numpy_int32_t(values[0], PyBUF_WRITABLE); if (unlikely(!__pyx_v_frame_positions.memview)) __PYX_ERR(0, 113, __pyx_L3_error)
    __pyx_v_atom_chainid = __Pyx_PyObject_to_MemoryviewSlice_dc_nn___pyx_t_5numpy_int32_t(values[1], PyBUF_WRITABLE); if (unlikely(!__pyx_v_atom_chainid.memview)) __PYX_ERR(0, 114, __pyx_L3_error)
    __pyx_v_grid = __Pyx_PyObject_to_MemoryviewSlice_d_dc_nn___pyx_t_5numpy_int32_t(values[2], PyBUF_WRITABLE); if (unlikely(!__pyx_v_grid.memview)) __PYX_ERR(0, 115, __pyx_L3_error)
  }
  goto __pyx_L6_skip;
  __pyx_L5_argtuple_error:;
  __Pyx_RaiseArgtupleInvalid("paint_frame_grid_2d", 1, 3, 3, __pyx_nargs); __PYX_ERR(0, 111, __pyx_L3_error)
  __pyx_L6_skip:;
  goto __pyx_L4_argument_unpacking_done;
  __pyx_L3_error:;
  for (Py_ssize_t __pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
    Py_XDECREF(values[__pyx_temp]);
  }
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_frame_positions, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_atom_chainid, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_grid, 1);
  __Pyx_AddTraceback("pimms.lemonade.kernels._pbc.paint_frame_grid_2d", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __Pyx_RefNannyFinishContext();
  return NULL;
  __pyx_L4_argument_unpacking_done:;
  __pyx_r = __pyx_pf_5pimms_8lemonade_7kernels_4_pbc_4paint_frame_grid_2d(__pyx_self, __pyx_v_frame_positions, __pyx_v_atom_chainid, __pyx_v_grid);
  int __pyx_lineno = 0;
  const char *__pyx_filename = NULL;
  int __pyx_clineno = 0;

  /* function exit code */
  for (Py_ssize_t __pyx_temp=0; __pyx_temp < (Py_ssize_t)(sizeof(values)/sizeof(values[0])); ++__pyx_temp) {
    Py_XDECREF(values[__pyx_temp]);
  }
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_frame_positions, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_atom_chainid, 1);
  __PYX_XCLEAR_MEMVIEW(&__pyx_v_grid, 1);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_5pimms_8lemonade_7kernels_4_pbc_4paint_frame_grid_2d(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_memviewslice __pyx_v_frame_positions, __Pyx_memviewslice __pyx_v_atom_chainid, __Pyx_memviewslice __pyx_v_grid) {
  Py_ssize_t __pyx_v_n;
  Py_ssize_t __pyx_v_i;
  PyObject *__pyx_r = NULL;
/* … */
  __pyx_t_4 = __Pyx_CyFunction_New(&__pyx_mdef_5pimms_8lemonade_7kernels_4_pbc_5paint_frame_grid_2d, 0, __pyx_mstate_global->__pyx_n_u_paint_frame_grid_2d, NULL, __pyx_mstate_global->__pyx_n_u_pimms_lemonade_kernels__pbc, __pyx_mstate_global->__pyx_d, ((PyObject *)__pyx_mstate_global->__pyx_codeobj_tab[2])); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 111, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  #if CYTHON_COMPILING_IN_CPYTHON && PY_VERSION_HEX >= 0x030E0000
  PyUnstable_Object_EnableDeferredRefcount(__pyx_t_4);
  #endif
  if (PyDict_SetItem(__pyx_mstate_global->__pyx_d, __pyx_mstate_global->__pyx_n_u_paint_frame_grid_2d, __pyx_t_4) < (0)) __PYX_ERR(0, 111, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
 112: @cython.wraparound(False)
 113: def paint_frame_grid_2d(cnp.int32_t[:, ::1] frame_positions,
 114:                         cnp.int32_t[::1] atom_chainid,
 115:                         cnp.int32_t[:, ::1] grid):
 116:     """2D counterpart of :func:`paint_frame_grid_3d` (ignores the z column)."""
+117:     cdef Py_ssize_t n = frame_positions.shape[0]
  __pyx_v_n = (__pyx_v_frame_positions.shape[0]);
 118:     cdef Py_ssize_t i
+119:     for i in range(n):
  __pyx_t_1 = __pyx_v_n;
  __pyx_t_2 = __pyx_t_1;
  for (__pyx_t_3 = 0; __pyx_t_3 < __pyx_t_2; __pyx_t_3+=1) {
    __pyx_v_i = __pyx_t_3;
+120:         grid[frame_positions[i, 0], frame_positions[i, 1]] = atom_chainid[i]
    __pyx_t_4 = __pyx_v_i;
    __pyx_t_5 = __pyx_v_i;
    __pyx_t_6 = 0;
    __pyx_t_7 = __pyx_v_i;
    __pyx_t_8 = 1;
    __pyx_t_9 = (*((__pyx_t_5numpy_int32_t *) ( /* dim=1 */ ((char *) (((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_frame_positions.data + __pyx_t_5 * __pyx_v_frame_positions.strides[0]) )) + __pyx_t_6)) )));
    __pyx_t_10 = (*((__pyx_t_5numpy_int32_t *) ( /* dim=1 */ ((char *) (((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_frame_positions.data + __pyx_t_7 * __pyx_v_frame_positions.strides[0]) )) + __pyx_t_8)) )));
    *((__pyx_t_5numpy_int32_t *) ( /* dim=1 */ ((char *) (((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ (__pyx_v_grid.data + __pyx_t_9 * __pyx_v_grid.strides[0]) )) + __pyx_t_10)) )) = (*((__pyx_t_5numpy_int32_t *) ( /* dim=0 */ ((char *) (((__pyx_t_5numpy_int32_t *) __pyx_v_atom_chainid.data) + __pyx_t_4)) )));
  }