





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 							lowerCAmelCase_      (				A_):
    UpperCamelCase__:     str     							=			[]
    if isinstance(_snake_case  ,_snake_case):
        for v in tree.values():
            shapes.extend(_fetch_dims(_snake_case))
    elif isinstance(_snake_case  ,(list, tuple)):
        for t in tree:
            shapes.extend(_fetch_dims(_snake_case))
    elif isinstance(_snake_case  ,torch.Tensor):
        shapes.append(tree.shape)
    else:
        raise ValueError("Not supported")

    return shapes



@torch.jit.ignore
def 							lowerCAmelCase_      (				A_  ,A_):
    UpperCamelCase__:     Dict     							=			[]
    for d in reversed(_snake_case):
        idx.append(flat_idx % d)
        UpperCamelCase__:     List[str]     							=			flat_idx // d

    return tuple(reversed(_snake_case))



@torch.jit.ignore
def 							lowerCAmelCase_      (				A_  ,A_  ,A_  ,A_ = None  ,A_ = None  ,):

    # start_edges and end_edges both indicate whether, starting from any given
    # dimension, the start/end index is at the top/bottom edge of the
    # corresponding tensor, modeled as a tree
    def reduce_edge_list(A_) -> None:
        UpperCamelCase__:     List[str]     							=			True
        for i in range(len(_snake_case)):
            UpperCamelCase__:     Dict     							=			-1 * (i + 1)
            l[reversed_idx] &= tally
            UpperCamelCase__:     Optional[Any]     							=			l[reversed_idx]

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

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

    UpperCamelCase__:     List[Tuple[slice, ...]]     							=			[]
    UpperCamelCase__:     List[slice]     							=			[]

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

    UpperCamelCase__:     Tuple[slice, ...]     							=			tuple(_snake_case)
    UpperCamelCase__:     List[Any]     							=			len(_snake_case)

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

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

        UpperCamelCase__:     Optional[Any]     							=			start[divergence_idx]
        return tuple(
            path + (slice(_snake_case  ,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

        UpperCamelCase__:     List[Any]     							=			end[divergence_idx]
        return tuple(
            path + (slice(_snake_case  ,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())
        UpperCamelCase__:     Dict     							=			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 							lowerCAmelCase_      (				A_  ,A_  ,A_  ,A_):
    UpperCamelCase__:     Tuple     							=			t.shape[:no_batch_dims]
    UpperCamelCase__:     Dict     							=			list(_flat_idx_to_idx(_snake_case  ,_snake_case))
    # _get_minimal_slice_set is inclusive
    UpperCamelCase__:     Any     							=			list(_flat_idx_to_idx(flat_end - 1  ,_snake_case))

    # Get an ordered list of slices to perform
    UpperCamelCase__:     List[str]     							=			_get_minimal_slice_set(
        _snake_case  ,_snake_case  ,_snake_case  ,)

    UpperCamelCase__:     Any     							=			[t[s] for s in slices]

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



def 							lowerCAmelCase_      (				A_  ,A_  ,A_  ,A_  ,A_ = False  ,A_ = None  ,A_ = False  ,):
    if not (len(_snake_case) > 0):
        raise ValueError("Must provide at least one input")

    UpperCamelCase__:     str     							=			[shape[:no_batch_dims] for shape in _fetch_dims(_snake_case)]
    UpperCamelCase__:     Dict     							=			tuple([max(_snake_case) for s in zip(*_snake_case)])

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

    UpperCamelCase__:     Dict[str, Any]     							=			tensor_tree_map(_prep_inputs  ,_snake_case)
    UpperCamelCase__:     int     							=			None
    if _out is not None:
        UpperCamelCase__:     Dict     							=			tensor_tree_map(lambda A_: t.view([-1] + list(t.shape[no_batch_dims:]))  ,_out)

    UpperCamelCase__:     Optional[Any]     							=			1
    for d in orig_batch_dims:
        flat_batch_dim *= d

    UpperCamelCase__:     Optional[Any]     							=			flat_batch_dim // chunk_size + (flat_batch_dim % chunk_size != 0)

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

    UpperCamelCase__:     int     							=			0
    UpperCamelCase__:     Dict     							=			prepped_outputs
    for _ in range(_snake_case):
        # Chunk the input
        if not low_mem:
            UpperCamelCase__:     List[str]     							=			_select_chunk
        else:
            UpperCamelCase__:     int     							=			partial(
                _chunk_slice  ,flat_start=_snake_case  ,flat_end=min(_snake_case  ,i + chunk_size)  ,no_batch_dims=len(_snake_case)  ,)

        UpperCamelCase__:     Dict[str, Any]     							=			tensor_tree_map(_snake_case  ,_snake_case)

        # Run the layer on the chunk
        UpperCamelCase__:     str     							=			layer(**_snake_case)

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

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

            def assign(A_  ,A_) -> None:
                for k, v in da.items():
                    if isinstance(_snake_case  ,_snake_case):
                        assign(_snake_case  ,da[k])
                    else:
                        if _add_into_out:
                            v[i : i + chunk_size] += da[k]
                        else:
                            UpperCamelCase__:     Union[str, Any]     							=			da[k]

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

        i += chunk_size

    UpperCamelCase__:     Dict     							=			tensor_tree_map(lambda A_: t.view(orig_batch_dims + t.shape[1:])  ,_snake_case)

    return out




class     _a  :

      """simple docstring"""


      def __init__(						self:       Union[str, Any]     ,  __lowerCamelCase:       Optional[int] = 512     ,  ):
          '''simple docstring'''


          UpperCamelCase__:     int     							=			max_chunk_size
          UpperCamelCase__:     Optional[int]     							=			None
          UpperCamelCase__:     Optional[tuple]     							=			None






      def        UpperCAmelCase_    (						self:       Any     ,  __lowerCamelCase:       Optional[Any]     ,  __lowerCamelCase:       Dict     ,  __lowerCamelCase:       str ):
          '''simple docstring'''


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

          if min_chunk_size >= self.max_chunk_size:
              return min_chunk_size

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

          def test_chunk_size(__lowerCamelCase:       Tuple ) -> bool:
              try:
                  with torch.no_grad():
                      fn(*SCREAMING_SNAKE_CASE__     ,  chunk_size=SCREAMING_SNAKE_CASE__ )
                  return True
              except RuntimeError:
                  return False

          UpperCamelCase__:     Any     							=			0
          UpperCamelCase__:     Dict     							=			len(SCREAMING_SNAKE_CASE__ ) - 1
          while i > min_viable_chunk_size_index:
              UpperCamelCase__:     int     							=			test_chunk_size(candidates[i] )
              if not viable:
                  UpperCamelCase__:     Dict     							=			(min_viable_chunk_size_index + i) // 2
              else:
                  UpperCamelCase__:     Any     							=			i
                  UpperCamelCase__:     Dict     							=			(i + len(SCREAMING_SNAKE_CASE__ ) - 1) // 2

          return candidates[min_viable_chunk_size_index]






      def        UpperCAmelCase_    (						self:       List[Any]     ,  __lowerCamelCase:       Optional[int]     ,  __lowerCamelCase:       Optional[int] ):
          '''simple docstring'''


          UpperCamelCase__:     List[Any]     							=			True
          for aa, aa in zip(SCREAMING_SNAKE_CASE__     ,  SCREAMING_SNAKE_CASE__ ):
              assert type(SCREAMING_SNAKE_CASE__ ) == type(SCREAMING_SNAKE_CASE__ )
              if isinstance(SCREAMING_SNAKE_CASE__     ,  (list, tuple) ):
                  consistent &= self._compare_arg_caches(SCREAMING_SNAKE_CASE__     ,  SCREAMING_SNAKE_CASE__ )
              elif isinstance(SCREAMING_SNAKE_CASE__     ,  SCREAMING_SNAKE_CASE__ ):
                  UpperCamelCase__:     str     							=			[v for _, v in sorted(aa.items()     ,  key=lambda __lowerCamelCase : x[0] )]
                  UpperCamelCase__:     Tuple     							=			[v for _, v in sorted(aa.items()     ,  key=lambda __lowerCamelCase : x[0] )]
                  consistent &= self._compare_arg_caches(SCREAMING_SNAKE_CASE__     ,  SCREAMING_SNAKE_CASE__ )
              else:
                  consistent &= aa == aa

          return consistent






      def        UpperCAmelCase_    (						self:       List[Any]     ,  __lowerCamelCase:       Optional[int]     ,  __lowerCamelCase:       Optional[Any]     ,  __lowerCamelCase:       str     ,  ):
          '''simple docstring'''


          UpperCamelCase__:     Union[str, Any]     							=			True
          UpperCamelCase__:     tuple     							=			tree_map(lambda __lowerCamelCase : a.shape if isinstance(SCREAMING_SNAKE_CASE__     ,  torch.Tensor ) else a     ,  SCREAMING_SNAKE_CASE__     ,  SCREAMING_SNAKE_CASE__ )
          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(SCREAMING_SNAKE_CASE__ )
              UpperCamelCase__:     Any     							=			self._compare_arg_caches(self.cached_arg_data     ,  SCREAMING_SNAKE_CASE__ )
          else:
              # Otherwise, we can reuse the precomputed value
              UpperCamelCase__:     Dict     							=			False

          if not consistent:
              UpperCamelCase__:     Dict     							=			self._determine_favorable_chunk_size(
                  SCREAMING_SNAKE_CASE__     ,  SCREAMING_SNAKE_CASE__     ,  SCREAMING_SNAKE_CASE__     ,  )
              UpperCamelCase__:     Any     							=			arg_data

          assert self.cached_chunk_size is not None

          return self.cached_chunk_size



"""simple docstring"""




def lowercase_  (   _snake_case						):
							if a < 0:
														raise ValueError("""Input value must be a positive integer"""						)
							elif isinstance(_snake_case   ,_snake_case						):
														raise TypeError("""Input value must be a 'int' type"""						)
							return bin(_snake_case						).count("""1"""						)


if __name__ == "__main__":
					import doctest

					doctest.testmod()




