code: def get_nodes_for(
      node_types: Dict[NodeType, NodeTypeConfigDict],
      existing_nodes: Dict[NodeType, int],
      head_node_type: NodeType,
      max_to_add: int,
      resources: List[ResourceDict],
      utilization_scorer: Callable[
          [NodeResources, ResourceDemands, str],
          Optional[UtilizationScore],
      ],
      strict_spread: bool = False,
  ) -> (Dict[NodeType, int], List[ResourceDict]):
      """Determine nodes to add given resource demands and constraints.
  
      Args:
          node_types: node types config.
          existing_nodes: counts of existing nodes already launched.
              This sets constraints on the number of new nodes to add.
          max_to_add: global constraint on nodes to add.
          resources: resource demands to fulfill.
          strict_spread: If true, each element in `resources` must be placed on a
              different node.
          utilization_scorer: A function that, given a node
              type, its resources, and resource demands, returns what its
              utilization would be.
  
      Returns:
          Dict of count to add for each node type, and residual of resources
          that still cannot be fulfilled.
      """
      nodes_to_add: Dict[NodeType, int] = collections.defaultdict(int)
  
      while resources and sum(nodes_to_add.values()) < max_to_add:
          utilization_scores = []
          for node_type in node_types:
              max_workers_of_node_type = node_types[node_type].get(
                  "max_workers", 0
              )
              if head_node_type == node_type:
                  # Add 1 to account for head node.
                  max_workers_of_node_type = (
                      max_workers_of_node_type + 1
                  )
              if (
                  existing_nodes.get(node_type, 0)
                  + nodes_to_add.get(node_type, 0)
                  >= max_workers_of_node_type
              ):
                  continue
              node_resources = node_types[node_type]["resources"]
              if strict_spread:
                  # If handling strict spread, only one bundle can be placed on
                  # the node.
                  score = utilization_scorer(
                      node_resources, [resources[0]], node_type
                  )
              else:
                  score = utilization_scorer(
                      node_resources, resources, node_type
                  )
              if score is not None:
                  utilization_scores.append((score, node_type))
  
          # Give up, no feasible node.
          if not utilization_scores:
              if not any(
                  is_placement_group_resource(resource)
                  for resources_dict in resources
                  for resource in resources_dict
              ):
                  logger.warning(
                      f"The autoscaler could not find a node type to satisfy the "
                      f"request: {resources}. Please specify a node type with the "
                      f"necessary resources."
                  )
              break
  
          utilization_scores = sorted(utilization_scores, reverse=True)
          best_node_type = utilization_scores[0][1]
          nodes_to_add[best_node_type] += 1
          if strict_spread:
              resources = resources[1:]
          else:
              allocated_resource = node_types[best_node_type][
                  "resources"
              ]
              residual, _ = get_bin_pack_residual(
                  [allocated_resource], resources
              )
              assert len(residual) < len(resources), (
                  resources,
                  residual,
              )
              resources = residual
  
      return nodes_to_add, resources
  
imports: import collections
  import copy
  import logging
  import os
  from abc import abstractmethod
  from functools import partial
  from typing import Callable, Dict, List, Optional, Tuple
  import ray
  from ray._private.gcs_utils import PlacementGroupTableData
  from ray.autoscaler._private.constants import AUTOSCALER_CONSERVE_GPU_NODES, AUTOSCALER_UTILIZATION_SCORER_KEY
  from ray.autoscaler._private.loader import load_function_or_class
  from ray.autoscaler._private.node_provider_availability_tracker import NodeAvailabilitySummary
  from ray.autoscaler._private.util import NodeID, NodeIP, NodeType, NodeTypeConfigDict, ResourceDict, is_placement_group_resource
  from ray.autoscaler.node_provider import NodeProvider
  from ray.autoscaler.tags import NODE_KIND_HEAD, NODE_KIND_UNMANAGED, NODE_KIND_WORKER, TAG_RAY_NODE_KIND, TAG_RAY_USER_NODE_TYPE
  from ray.core.generated.common_pb2 import PlacementStrategy
label_desc: Use image node_id 3 for calling a global function with example usage: get_nodes_for(node_types, existing_nodes, head_node_type, max_to_add, resources, utilization_scorer, strict_spread) and returns: nodes_to_add,  resources