docstr: Using the greedy heuristic from
  "A Greedy Algorithm for the General Multidimensional Knapsack
  Problem", Akcay, Li, Xu, Annals of Operations Research, 2012
  
  Args:
      jobs (list):    list of jobs
docstr_tok: ["Using","the","greedy","heuristic","from","''","A","Greedy","Algorithm","for","the","General","Multidimensional","Knapsack","Problem","''",",","Akcay",",","Li",",","Xu",",","Annals","of","Operations","Research",",","2012","Args",":","jobs","(","list",")",":","list","of","jobs"]
code: def job_selector_greedy(self, jobs):
      """
      Using the greedy heuristic from
      "A Greedy Algorithm for the General Multidimensional Knapsack
      Problem", Akcay, Li, Xu, Annals of Operations Research, 2012
  
      Args:
          jobs (list):    list of jobs
      """
      with self._lock:
          if not self.resources["_cores"]:
              return set()
          # each job is an item with one copy (0-1 MDKP)
          n = len(jobs)
          x = [0] * n  # selected jobs
          E = set(range(n))  # jobs still free to select
          u = [1] * n
          a = list(map(self.job_weight, jobs))  # resource usage of jobs
  
          async def rewards():
              return [await self.job_reward(job) for job in jobs]
  
          c = async_run(rewards())  # job rewards
  
          def calc_reward():
              return [c_j * y_j for c_j, y_j in zip(c, y)]
  
          b = [
              self.resources[name] for name in self.global_resources
          ]  # resource capacities
  
          while True:
              # Step 2: compute effective capacities
              y = [
                  (
                      min(
                          (
                              min(u[j], b_i // a_j_i)
                              if a_j_i > 0
                              else u[j]
                          )
                          for b_i, a_j_i in zip(b, a[j])
                          if a_j_i
                      )
                      if j in E
                      else 0
                  )
                  for j in range(n)
              ]
              if not any(y):
                  break
              y = [
                  (max(1, int(self.greediness * y_j)) if y_j > 0 else 0)
                  for y_j in y
              ]
  
              # Step 3: compute rewards on cumulative sums
              reward = calc_reward()
              j_sel = max(E, key=reward.__getitem__)  # argmax
  
              # Step 4: batch increment
              y_sel = y[j_sel]
  
              # Step 5: update information
              x[j_sel] += y_sel
              b = [
                  b_i - (a_j_i * y_sel)
                  for b_i, a_j_i in zip(b, a[j_sel])
              ]
              u[j_sel] -= y_sel
              if not u[j_sel] or self.greediness == 1:
                  E.remove(j_sel)
              if not E:
                  break
  
          solution = set(job for job, sel in zip(jobs, x) if sel)
          # update resources
          for name, b_i in zip(self.global_resources, b):
              self.resources[name] = b_i
          return solution
  
imports: import asyncio
  from collections import defaultdict
  import math
  import os, signal, sys
  import threading
  from functools import partial
  from itertools import chain, accumulate
  from contextlib import ContextDecorator
  from snakemake_interface_executor_plugins.scheduler import JobSchedulerExecutorInterface
  from snakemake_interface_executor_plugins.registry import ExecutorPluginRegistry
  from snakemake_interface_executor_plugins.registry import Plugin
  from snakemake_interface_executor_plugins.settings import ExecMode
  from snakemake.common import async_run
  from snakemake.exceptions import RuleException, WorkflowError, print_exception
  from snakemake.logging import logger
  from fractions import Fraction
label_desc: Use image node_id 20 for calling the JobScheduler obj's underlying member method code with example usage: obj.job_selector_greedy(jobs) and returns: solution, set, unknown, unknown