



"""simple docstring"""
import logging
import math
from functools import partial
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union

import torch

from .tensor_utils import tensor_tree_map, tree_map


def 			_SCREAMING_SNAKE_CASE						(__lowerCAmelCase							) ->						List[Tuple[int, ...]]:







  '''simple docstring'''


  lowercase_								= []
  if isinstance(lowercase_ ,  lowercase_							):
    for v in tree.values():
      shapes.extend(_fetch_dims(lowercase_							)							)
  elif isinstance(lowercase_ ,  (list, tuple)							):
    for t in tree:
      shapes.extend(_fetch_dims(lowercase_							)							)
  elif isinstance(lowercase_ ,  torch.Tensor							):
    shapes.append(tree.shape							)
  else:
    raise ValueError("""Not supported"""							)

  return shapes


@torch.jit.ignore
def 			_SCREAMING_SNAKE_CASE						(__lowerCAmelCase ,  __lowerCAmelCase							) ->						Tuple[int, ...]:







  '''simple docstring'''


  lowercase_								= []
  for d in reversed(lowercase_							):
    idx.append(flat_idx % d							)
    lowercase_								= flat_idx // d

  return tuple(reversed(lowercase_							)							)


@torch.jit.ignore
def 			_SCREAMING_SNAKE_CASE						(__lowerCAmelCase ,  __lowerCAmelCase ,  __lowerCAmelCase ,  __lowerCAmelCase = None ,  __lowerCAmelCase = None ,  ) ->						List[Tuple[slice, ...]]:







  '''simple docstring'''


  def reduce_edge_list(__lowerCAmelCase							) -> None:
    lowercase_								= True
    for i in range(len(lowercase_							)							):
      lowercase_								= -1 * (i + 1)
      l[reversed_idx] &= tally
      lowercase_								= l[reversed_idx]

  if start_edges is None:
    lowercase_								= [s == 0 for s in start]
    reduce_edge_list(lowercase_							)
  if end_edges is None:
    lowercase_								= [e == (d - 1) for e, d in zip(lowercase_ ,  lowercase_							)]
    reduce_edge_list(lowercase_							)

  # Base cases. Either start/end are empty and we're done, or the final,
  # one-dimensional tensor can be simply sliced
  if len(lowercase_							) == 0:
    return [()]
  elif len(lowercase_							) == 1:
    return [(slice(start[0] ,  end[0] + 1							),)]

  lowercase_								= []
  lowercase_								= []

  # Dimensions common to start and end can be selected directly
  for s, e in zip(lowercase_ ,  lowercase_							):
    if s == e:
      path_list.append(slice(lowercase_ ,  s + 1							)							)
    else:
      break

  lowercase_								= tuple(lowercase_							)
  lowercase_								= len(lowercase_							)

  # start == end, and we're done
  if divergence_idx == len(lowercase_							):
    return [path]

  def upper() -> Tuple[Tuple[slice, ...], ...]:
    assert start_edges is not None
    assert end_edges is not None

    lowercase_								= start[divergence_idx]
    return tuple(
        path + (slice(lowercase_ ,  sdi + 1							),) + s
        for s in _get_minimal_slice_set(
            start[divergence_idx + 1 :] ,  [d - 1 for d in dims[divergence_idx + 1 :]] ,  dims[divergence_idx + 1 :] ,  start_edges=start_edges[divergence_idx + 1 :] ,  end_edges=[True for _ in end_edges[divergence_idx + 1 :]] ,  )							)

  def lower() -> Tuple[Tuple[slice, ...], ...]:
    assert start_edges is not None
    assert end_edges is not None

    lowercase_								= end[divergence_idx]
    return tuple(
        path + (slice(lowercase_ ,  edi + 1							),) + s
        for s in _get_minimal_slice_set(
            [0 for _ in start[divergence_idx + 1 :]] ,  end[divergence_idx + 1 :] ,  dims[divergence_idx + 1 :] ,  start_edges=[True for _ in start_edges[divergence_idx + 1 :]] ,  end_edges=end_edges[divergence_idx + 1 :] ,  )							)

  # If both start and end are at the edges of the subtree rooted at
  # divergence_idx, we can just select the whole subtree at once
  if start_edges[divergence_idx] and end_edges[divergence_idx]:
    slices.append(path + (slice(start[divergence_idx] ,  end[divergence_idx] + 1							),)							)
  # If just start is at the edge, we can grab almost all of the subtree,
  # treating only the ragged bottom edge as an edge case
  elif start_edges[divergence_idx]:
    slices.append(path + (slice(start[divergence_idx] ,  end[divergence_idx]							),)							)
    slices.extend(lower()							)
  # Analogous to the previous case, but the top is ragged this time
  elif end_edges[divergence_idx]:
    slices.extend(upper()							)
    slices.append(path + (slice(start[divergence_idx] + 1 ,  end[divergence_idx] + 1							),)							)
  # If both sides of the range are ragged, we need to handle both sides
  # separately. If there's contiguous meat in between them, we can index it
  # in one big chunk
  else:
    slices.extend(upper()							)
    lowercase_								= end[divergence_idx] - start[divergence_idx]
    if middle_ground > 1:
      slices.append(path + (slice(start[divergence_idx] + 1 ,  end[divergence_idx]							),)							)
    slices.extend(lower()							)

  return slices


@torch.jit.ignore
def 			_SCREAMING_SNAKE_CASE						(__lowerCAmelCase ,  __lowerCAmelCase ,  __lowerCAmelCase ,  __lowerCAmelCase							) ->						torch.Tensor:







  '''simple docstring'''


  lowercase_								= t.shape[:no_batch_dims]
  lowercase_								= list(_flat_idx_to_idx(lowercase_ ,  lowercase_							)							)
  # _get_minimal_slice_set is inclusive
  lowercase_								= list(_flat_idx_to_idx(flat_end - 1 ,  lowercase_							)							)

  # Get an ordered list of slices to perform
  lowercase_								= _get_minimal_slice_set(
      lowercase_ ,  lowercase_ ,  lowercase_ ,  )

  lowercase_								= [t[s] for s in slices]

  return torch.cat([s.view((-1,) + t.shape[no_batch_dims:]							) for s in sliced_tensors]							)


def 			_SCREAMING_SNAKE_CASE						(__lowerCAmelCase ,  __lowerCAmelCase ,  __lowerCAmelCase ,  __lowerCAmelCase ,  __lowerCAmelCase = False ,  __lowerCAmelCase = None ,  __lowerCAmelCase = False ,  ) ->						Any:







  '''simple docstring'''


  if not (len(lowercase_							) > 0):
    raise ValueError("""Must provide at least one input"""							)

  lowercase_								= [shape[:no_batch_dims] for shape in _fetch_dims(lowercase_							)]
  lowercase_								= tuple([max(lowercase_							) for s in zip(*lowercase_							)]							)

  def _prep_inputs(__lowerCAmelCase							) -> torch.Tensor:
    if not low_mem:
      if not sum(t.shape[:no_batch_dims]							) == no_batch_dims:
        lowercase_								= t.expand(orig_batch_dims + t.shape[no_batch_dims:]							)
      lowercase_								= t.reshape(-1 ,  *t.shape[no_batch_dims:]							)
    else:
      lowercase_								= t.expand(orig_batch_dims + t.shape[no_batch_dims:]							)
    return t

  lowercase_								= tensor_tree_map(_prep_inputs ,  lowercase_							)
  lowercase_								= None
  if _out is not None:
    lowercase_								= tensor_tree_map(lambda __lowerCAmelCase							: t.view([-1] + list(t.shape[no_batch_dims:]							)							) ,  _out							)

  lowercase_								= 1
  for d in orig_batch_dims:
    flat_batch_dim *= d

  lowercase_								= flat_batch_dim // chunk_size + (flat_batch_dim % chunk_size != 0)

  def _select_chunk(__lowerCAmelCase							) -> torch.Tensor:
    return t[i : i + chunk_size] if t.shape[0] != 1 else t

  lowercase_								= 0
  lowercase_								= prepped_outputs
  for _ in range(lowercase_							):
    # Chunk the input
    if not low_mem:
      lowercase_								= _select_chunk
    else:
      lowercase_								= partial(
          _chunk_slice ,  flat_start=lowercase_ ,  flat_end=min(lowercase_ ,  i + chunk_size							) ,  no_batch_dims=len(lowercase_							) ,  )

    lowercase_								= tensor_tree_map(lowercase_ ,  lowercase_							)

    # Run the layer on the chunk
    lowercase_								= layer(**lowercase_							)

    # Allocate space for the output
    if out is None:
      lowercase_								= tensor_tree_map(lambda __lowerCAmelCase							: t.new_zeros((flat_batch_dim,) + t.shape[1:]							) ,  lowercase_							)

    # Put the chunk in its pre-allocated space
    if isinstance(lowercase_ ,  lowercase_							):

      def assign(__lowerCAmelCase ,  __lowerCAmelCase							) -> None:
        for k, v in da.items():
          if isinstance(lowercase_ ,  lowercase_							):
            assign(lowercase_ ,  da[k]							)
          else:
            if _add_into_out:
              v[i : i + chunk_size] += da[k]
            else:
              lowercase_								= da[k]

      assign(lowercase_ ,  lowercase_							)
    elif isinstance(lowercase_ ,  lowercase_							):
      for xa, xa in zip(lowercase_ ,  lowercase_							):
        if _add_into_out:
          xa[i : i + chunk_size] += xa
        else:
          lowercase_								= xa
    elif isinstance(lowercase_ ,  torch.Tensor							):
      if _add_into_out:
        out[i : i + chunk_size] += output_chunk
      else:
        lowercase_								= output_chunk
    else:
      raise ValueError("""Not supported"""							)

    i += chunk_size

  lowercase_								= tensor_tree_map(lambda __lowerCAmelCase							: t.view(orig_batch_dims + t.shape[1:]							) ,  lowercase_							)

  return out


class    SCREAMING_SNAKE_CASE__ :

    def __init__(				self			:				Optional[int] ,  lowerCAmelCase_			:				int = 5_1_2 ,  ):







      """simple docstring"""







      lowercase_								= max_chunk_size
      lowercase_								= None
      lowercase_								= None

    def 					_UpperCAmelCase	(				self			:				List[Any] ,  lowerCAmelCase_			:				Callable ,  lowerCAmelCase_			:				tuple ,  lowerCAmelCase_			:				int):







      """simple docstring"""







      logging.info("""Tuning chunk size...""")

      if min_chunk_size >= self.max_chunk_size:
        return min_chunk_size

      lowercase_								= [2**l for l in range(int(math.log(self.max_chunk_size ,  2)) + 1)]
      lowercase_								= [c for c in candidates if c > min_chunk_size]
      lowercase_								= [min_chunk_size] + candidates
      candidates[-1] += 4

      def test_chunk_size(lowerCAmelCase_			:				int) -> bool:
        try:
          with torch.no_grad():
            fn(*__a ,  chunk_size=__a)
          return True
        except RuntimeError:
          return False

      lowercase_								= 0
      lowercase_								= len(__a) - 1
      while i > min_viable_chunk_size_index:
        lowercase_								= test_chunk_size(candidates[i])
        if not viable:
          lowercase_								= (min_viable_chunk_size_index + i) // 2
        else:
          lowercase_								= i
          lowercase_								= (i + len(__a) - 1) // 2

      return candidates[min_viable_chunk_size_index]

    def 					_UpperCAmelCase	(				self			:				List[Any] ,  lowerCAmelCase_			:				Iterable ,  lowerCAmelCase_			:				Iterable):







      """simple docstring"""







      lowercase_								= True
      for aa, aa in zip(__a ,  __a):
        assert type(__a) == type(__a)
        if isinstance(__a ,  (list, tuple)):
          consistent &= self._compare_arg_caches(__a ,  __a)
        elif isinstance(__a ,  __a):
          lowercase_								= [v for _, v in sorted(aa.items() ,  key=lambda lowerCAmelCase_: x[0])]
          lowercase_								= [v for _, v in sorted(aa.items() ,  key=lambda lowerCAmelCase_: x[0])]
          consistent &= self._compare_arg_caches(__a ,  __a)
        else:
          consistent &= aa == aa

      return consistent




    def 					_UpperCAmelCase	(				self			:				str ,  lowerCAmelCase_			:				Callable ,  lowerCAmelCase_			:				tuple ,  lowerCAmelCase_			:				int ,  ):







      """simple docstring"""







      lowercase_								= True
      lowercase_								= tree_map(lambda lowerCAmelCase_: a.shape if isinstance(__a ,  torch.Tensor) else a ,  __a ,  __a)
      if self.cached_arg_data is not None:
        # If args have changed shape/value, we need to re-tune
        assert len(self.cached_arg_data) == len(__a)
        lowercase_								= self._compare_arg_caches(self.cached_arg_data ,  __a)
      else:
        # Otherwise, we can reuse the precomputed value
        lowercase_								= False

      if not consistent:
        lowercase_								= self._determine_favorable_chunk_size(
            __a ,  __a ,  __a ,  )
        lowercase_								= arg_data

      assert self.cached_chunk_size is not None

      return self.cached_chunk_size





"""simple docstring"""
def 			_SCREAMING_SNAKE_CASE						(__lowerCAmelCase							) ->						list[list[int]]:







  '''simple docstring'''


  lowercase_								= []
  if len(__lowerCAmelCase							) == 1:
    return [nums.copy()]
  for _ in range(len(__lowerCAmelCase							)							):
    lowercase_								= nums.pop(0							)
    lowercase_								= permute(__lowerCAmelCase							)
    for perm in permutations:
      perm.append(__lowerCAmelCase							)
    result.extend(__lowerCAmelCase							)
    nums.append(__lowerCAmelCase							)
  return result


def 			_SCREAMING_SNAKE_CASE						(__lowerCAmelCase							) ->						Tuple:







  '''simple docstring'''


  def backtrack(__lowerCAmelCase							):
    if start == len(__lowerCAmelCase							) - 1:
      output.append(nums[:]							)
    else:
      for i in range(__lowerCAmelCase ,  len(__lowerCAmelCase							)							):
        lowercase_					,    lowercase_								= nums[i], nums[start]
        backtrack(start + 1							)
        lowercase_					,    lowercase_								= nums[i], nums[start]  # backtrack

  lowercase_								= []
  backtrack(0							)
  return output


if __name__ == "__main__":
       import doctest

       # use res to print the data in permute2 function
       UpperCAmelCase						:		Optional[Any]			= permutea([1, 2, 3])
       print(res)
       doctest.testmod()

