code: def find_thread_assignment(
      self, packet_nr_to_dofs, local_row_cost, thread_count
  ):
      thread_assignments = [[] for i in range(thread_count)]
      thread_costs = np.zeros(thread_count)
  
      for packet_nr, packet_dofs in enumerate(packet_nr_to_dofs):
          row_costs_and_numbers = sorted(
              [(local_row_cost[i], i) for i in packet_dofs],
              reverse=True,
          )
  
          base_thread_nr = packet_nr * self.threads_per_packet
          thread_offset = 0
  
          # zigzag assignment
          step = 1
          for row_cost, row_number in row_costs_and_numbers:
              ti = base_thread_nr + thread_offset
              thread_assignments[ti].append(row_number)
              thread_costs[ti] += row_cost
  
              if thread_offset + step >= self.threads_per_packet:
                  step = -1
              elif thread_offset + step < 0:
                  step = 1
              else:
                  thread_offset += step
  
      return thread_assignments, thread_costs
  
file_path: pycuda/pycuda/sparse/packeted.py
imports: from __future__ import division
  from __future__ import absolute_import
  from __future__ import print_function
  from pytools import memoize_method
  import pycuda.gpuarray
  from pycuda.compiler import SourceModule
  import numpy
label_desc: Use image node_id 4 for calling the PacketedSpMV obj's underlying member method code with example usage: obj.find_thread_assignment(packet_nr_to_dofs, local_row_cost, thread_count) and returns: thread_assignments,  thread_costs