class_docstr: Generates and executes code that performs prefix sums ("scans") on
  arbitrary types, with many possible tweaks.
  
  Usage example::
  
      from pyopencl.scan import GenericScanKernel
      knl = GenericScanKernel(
              context, np.int32,
              arguments="__global int *ary",
              input_expr="ary[i]",
              scan_expr="a+b", neutral="0",
              output_statement="ary[i+1] = item;")
  
      a = cl.array.arange(queue, 10000, dtype=np.int32)
      knl(a, queue=queue)
  
  .. automethod:: __init__
  .. automethod:: __call__
class_docstr_tok: ["Generates","and","executes","code","that","performs","prefix","sums","(","``","scans","''",")","on","arbitrary","types",",","with","many","possible","tweaks",".","Usage","example",":",":","from","pyopencl.scan","import","GenericScanKernel","knl","=","GenericScanKernel","(","context",",","np.int32",",","arguments=","''","__global","int","*","ary","''",",","input_expr=","''","ary","[","i","]","''",",","scan_expr=","''","a+b","''",",","neutral=","''","0","''",",","output_statement=","''","ary","[","i+1","]","=","item",";","''",")","a","=","cl.array.arange","(","queue",",","10000",",","dtype=np.int32",")","knl","(","a",",","queue=queue",")","..","automethod",":",":","__init__","..","automethod",":",":","__call__"]
docstr: |std-enqueue-blurb|
  
  .. note::
  
      The returned :class:`pyopencl.Event` corresponds only to part of the
      execution of the scan. It is not suitable for profiling.
  
  :arg queue: queue on which to execute the scan. If not given, the
      queue of the first :class:`pyopencl.array.Array` in *args* is used
  :arg allocator: an allocator for the temporary arrays and results. If
      not given, the allocator of the first :class:`pyopencl.array.Array`
      in *args* is used.
  :arg size: specify the length of the scan to be carried out. If not
      given, this length is inferred from the first argument
  :arg wait_for: a :class:`list` of events to wait for.
docstr_tok: ["|std-enqueue-blurb|","..","note",":",":","The","returned",":","class",":","`","pyopencl.Event","`","corresponds","only","to","part","of","the","execution","of","the","scan",".","It","is","not","suitable","for","profiling",".",":","arg","queue",":","queue","on","which","to","execute","the","scan",".","If","not","given",",","the","queue","of","the","first",":","class",":","`","pyopencl.array.Array","`","in","*","args","*","is","used",":","arg","allocator",":","an","allocator","for","the","temporary","arrays","and","results",".","If","not","given",",","the","allocator","of","the","first",":","class",":","`","pyopencl.array.Array","`","in","*","args","*","is","used",".",":","arg","size",":","specify","the","length","of","the","scan","to","be","carried","out",".","If","not","given",",","this","length","is","inferred","from","the","first","argument",":","arg","wait_for",":","a",":","class",":","`","list","`","of","events","to","wait","for","."]
code: def __call__(self, *args: Any, **kwargs: Any) -> cl.Event:
      """
      |std-enqueue-blurb|
  
      .. note::
  
          The returned :class:`pyopencl.Event` corresponds only to part of the
          execution of the scan. It is not suitable for profiling.
  
      :arg queue: queue on which to execute the scan. If not given, the
          queue of the first :class:`pyopencl.array.Array` in *args* is used
      :arg allocator: an allocator for the temporary arrays and results. If
          not given, the allocator of the first :class:`pyopencl.array.Array`
          in *args* is used.
      :arg size: specify the length of the scan to be carried out. If not
          given, this length is inferred from the first argument
      :arg wait_for: a :class:`list` of events to wait for.
      """
  
      # {{{ argument processing
  
      allocator = kwargs.get("allocator")
      queue = kwargs.get("queue")
      n = kwargs.get("size")
      wait_for = kwargs.get("wait_for")
  
      if wait_for is None:
          wait_for = []
      else:
          wait_for = list(wait_for)
  
      if len(args) != len(self.parsed_args):
          raise TypeError(
              f"expected {len(self.parsed_args)} arguments, got {len(args)}"
          )
  
      first_array = args[self.first_array_idx]
      allocator = allocator or first_array.allocator
      queue = queue or first_array.queue
  
      if n is None:
          (n,) = first_array.shape
  
      if n == 0:
          # We're done here. (But pretend to return an event.)
          return cl.enqueue_marker(queue, wait_for=wait_for)
  
      data_args = []
      for arg_descr, arg_val in zip(self.parsed_args, args):
          from pyopencl.tools import VectorArg
  
          if isinstance(arg_descr, VectorArg):
              data_args.append(arg_val.base_data)
              if arg_descr.with_offset:
                  data_args.append(arg_val.offset)
              wait_for.extend(arg_val.events)
          else:
              data_args.append(arg_val)
  
      # }}}
  
      l1_info = self.first_level_scan_info
      l2_info = self.second_level_scan_info
  
      # see CL source above for terminology
      unit_size = l1_info.wg_size * l1_info.k_group_size
      max_intervals = 3 * max(
          dev.max_compute_units for dev in self.devices
      )
  
      from pytools import uniform_interval_splitting
  
      interval_size, num_intervals = uniform_interval_splitting(
          n, unit_size, max_intervals
      )
  
      # {{{ allocate some buffers
  
      interval_results = cl.array.empty(
          queue, num_intervals, dtype=self.dtype, allocator=allocator
      )
  
      partial_scan_buffer = cl.array.empty(
          queue, n, dtype=self.dtype, allocator=allocator
      )
  
      if self.store_segment_start_flags:
          segment_start_flags = cl.array.empty(
              queue, n, dtype=np.bool_, allocator=allocator
          )
  
      # }}}
  
      # {{{ first level scan of interval (one interval per block)
  
      scan1_args = data_args + [
          partial_scan_buffer.data,
          n,
          interval_size,
          interval_results.data,
      ]
  
      if self.is_segmented:
          first_segment_start_in_interval = cl.array.empty(
              queue,
              num_intervals,
              dtype=self.index_dtype,
              allocator=allocator,
          )
          scan1_args.append(first_segment_start_in_interval.data)
  
      if self.store_segment_start_flags:
          scan1_args.append(segment_start_flags.data)
  
      l1_evt = l1_info.kernel(
          queue,
          (num_intervals,),
          (l1_info.wg_size,),
          *scan1_args,
          g_times_l=True,
          wait_for=wait_for,
      )
  
      # }}}
  
      # {{{ second level scan of per-interval results
  
      # can scan at most one interval
      assert interval_size >= num_intervals
  
      scan2_args = data_args + [
          interval_results.data,  # interval_sums
      ]
      if self.is_segmented:
          scan2_args.append(first_segment_start_in_interval.data)
      scan2_args = scan2_args + [
          interval_results.data,  # partial_scan_buffer
          num_intervals,
          interval_size,
      ]
  
      l2_evt = l2_info.kernel(
          queue,
          (1,),
          (l1_info.wg_size,),
          *scan2_args,
          g_times_l=True,
          wait_for=[l1_evt],
      )
  
      # }}}
  
      # {{{ update intervals with result of interval scan
  
      upd_args = data_args + [
          n,
          interval_size,
          interval_results.data,
          partial_scan_buffer.data,
      ]
      if self.is_segmented:
          upd_args.append(first_segment_start_in_interval.data)
      if self.store_segment_start_flags:
          upd_args.append(segment_start_flags.data)
  
      return self.final_update_info.kernel(
          queue,
          (num_intervals,),
          (self.final_update_info.update_wg_size,),
          *upd_args,
          g_times_l=True,
          wait_for=[l2_evt],
      )
  
imports: from abc import ABC, abstractmethod
  from dataclasses import dataclass
  from typing import Any, Dict, List, Optional, Set, Tuple, Union
  import numpy
  import pyopencl
  import pyopencl.array
  from pyopencl.tools import KernelTemplateBase, DtypedArgument, bitlog2, context_dependent_memoize, dtype_to_ctype, get_arg_list_scalar_arg_dtypes, get_arg_offset_adjuster_code, _process_code_for_macro, _NumpyTypesKeyBuilder
  import pyopencl._mymako
  from pyopencl._cluda import CLUDA_PREAMBLE
  from pytools.persistent_dict import WriteOncePersistentDict
  import logging
label_desc: Use image node_id 5 for calling the GenericScanKernel obj's underlying member method code with example usage: obj.__call__() and returns: self, cl