class_docstr: Performs Value Range Analysis on LoopBody's fx graph by calling BoundVars.run()
  It exposes the ranges of the nodes in the `bounds` variable
  
  Note. A current limitation of this analysis is that it just works on a per-loop basis.
  We should be able to propagate the bounds between across the whole graph. This may benefit
  the case a bounded variable is returned by a kernel and fed into another.
class_docstr_tok: ["Performs","Value","Range","Analysis","on","LoopBody","'s","fx","graph","by","calling","BoundVars.run","(",")","It","exposes","the","ranges","of","the","nodes","in","the","`","bounds","`","variable","Note",".","A","current","limitation","of","this","analysis","is","that","it","just","works","on","a","per-loop","basis",".","We","should","be","able","to","propagate","the","bounds","between","across","the","whole","graph",".","This","may","benefit","the","case","a","bounded","variable","is","returned","by","a","kernel","and","fed","into","another","."]
code: def swap_submodules(
      self, submodules: Dict[str, Callable[..., Any]]
  ) -> Dict[str, Callable[..., ValueRanges]]:
      result: Dict[str, Callable[..., ValueRanges]] = {}
      for key in submodules.keys():
          if key == "get_index":
              result[key] = self.get_index
          elif "masked_subblock" in key:
              subblock = self.loop_body.subblocks[key]
              # The result within the lambda will reference to the final
              # set of modules at the end of the for-loop as it stores a reference to it
  
              # bind subblock in a function because python lambdas close over by reference
              # moving the lambda out of make_fn would close over the reference to subblock,
              # so all lambdas would have the same subblock reference that is the final
              # subblock in the loop
              def make_fn(subblock):
                  return lambda mask, value: self.masked_subblock(
                      subblock, self._bounds, mask, value, result
                  )
  
              result[key] = make_fn(subblock)
  
          elif "set_indirect" in key:
              idx = int(key[len("set_indirect") :])
              var = self.loop_body.indirect_vars[idx]
              indirect = partial(self.set_indirect, var)
              result[key] = indirect
          else:
              assert "scan" in key
              result[key] = submodules[key]
  
      return result
  
file_path: pytorch/torch/_inductor/bounds.py
imports: import operator
  from functools import partial
  from typing import Any, Callable, Dict
  from sympy import Expr
  import torch
  from torch.utils._sympy.value_ranges import bound_sympy, ValueRangeAnalysis, ValueRanges
  from .ir import InterpreterShim, LoopBody, LoopBodyBlock
  from .utils import cache_on_self, dominated_nodes
  from .virtualized import V
label_desc: Use image node_id 3 for calling the BoundVars obj's underlying member method code with example usage: obj.swap_submodules(submodules) and returns: result, function