code: def get_resources_per_job(
      self,
      total_num_cpus,
      num_jobs,
      minimum_cpu_per_job,
      model_estimate_memory_usage=None,
      wrap_resources_per_job_into_placement_group=False,
      user_resources_per_job=None,
      **kwargs,
  ):
      if user_resources_per_job is not None:
          cpu_per_job = user_resources_per_job.get("num_cpus", 0)
          assert (
              cpu_per_job <= total_num_cpus
          ), f"Detected model level cpu requirement = {cpu_per_job} > total cpu granted to AG predictor = {total_num_cpus}"
          assert (
              cpu_per_job >= minimum_cpu_per_job
          ), f"The model requires minimum cpu {minimum_cpu_per_job}, but you only specified {cpu_per_job}"
          num_parallel_jobs = total_num_cpus // cpu_per_job
          batches = math.ceil(num_jobs / num_parallel_jobs)
      else:
          cpu_per_job = max(
              minimum_cpu_per_job, int(total_num_cpus // num_jobs)
          )
          max_jobs_in_parallel_memory = num_jobs
  
          if model_estimate_memory_usage is not None:
              mem_available = (
                  ResourceManager.get_available_virtual_mem()
              )
              # calculate how many jobs can run in parallel given memory available
              max_jobs_in_parallel_memory = max(
                  1, int(mem_available // model_estimate_memory_usage)
              )
          num_parallel_jobs = min(
              num_jobs,
              total_num_cpus // cpu_per_job,
              max_jobs_in_parallel_memory,
          )
          if num_parallel_jobs == 0:
              error_msg = (
                  "Cannot train model with provided resources! "
                  f"num_cpus=={total_num_cpus} | "
                  f"min_cpus=={minimum_cpu_per_job}"
              )
              if model_estimate_memory_usage is not None:
                  error_msg += (
                      f" | mem_available=={mem_available} | "
                      f"model_estimate_memory_usage=={model_estimate_memory_usage}"
                  )
              raise AssertionError(error_msg)
          cpu_per_job = int(
              total_num_cpus // num_parallel_jobs
          )  # update cpu_per_job in case memory is not enough and can use more cores for each job
          batches = math.ceil(num_jobs / num_parallel_jobs)
  
      resources_per_job = dict(cpu=cpu_per_job)
      if wrap_resources_per_job_into_placement_group:
          resources_per_job = (
              self.wrap_resources_per_job_into_placement_group(
                  resources_per_job
              )
          )
  
      resources_info = dict(
          resources_per_job=resources_per_job,
          num_parallel_jobs=num_parallel_jobs,
          batches=batches,
          cpu_per_job=cpu_per_job,
      )
      logger.log(
          10,
          f"Resources info for {self.__class__.__name__}: {resources_info}",
      )
  
      return resources_info
  
imports: import logging
  import math
  from abc import ABC, abstractmethod
  from autogluon.common.utils.resource_utils import ResourceManager
label_desc: Use image node_id 2 for calling the CpuResourceCalculator obj's underlying member method code with example usage: obj.get_resources_per_job(total_num_cpus, num_jobs, minimum_cpu_per_job, model_estimate_memory_usage, wrap_resources_per_job_into_placement_group, user_resources_per_job) and returns: resources_info