




'''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       _UpperCamelCase			(_lowerCamelCase				:  Union[dict, list, tuple, torch.Tensor]						)-> List[Tuple[int, ...]]:




     '''simple docstring'''





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

     return shapes







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




     '''simple docstring'''





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

     return tuple(reversed(_lowerCamelCase						)						)







@torch.jit.ignore
def       _UpperCamelCase			(_lowerCamelCase				:  Sequence[int] ,						_lowerCamelCase				:  Sequence[int] ,						_lowerCamelCase				:  Sequence[int] ,						_lowerCamelCase				:  Optional[Sequence[bool]] = None ,						_lowerCamelCase				:  Optional[Sequence[bool]] = None ,						)-> List[Tuple[slice, ...]]:




     '''simple docstring'''





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

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

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

     __snake_case            =	[]
     __snake_case            =	[]

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

     __snake_case            =	tuple(_lowerCamelCase						)
     __snake_case            =	len(_lowerCamelCase						)

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

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

          __snake_case            =	start[divergence_idx]
          return tuple(
              path + (slice(_lowerCamelCase ,						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

          __snake_case            =	end[divergence_idx]
          return tuple(
              path + (slice(_lowerCamelCase ,						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()						)
          __snake_case            =	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       _UpperCamelCase			(_lowerCamelCase				:  torch.Tensor ,						_lowerCamelCase				:  int ,						_lowerCamelCase				:  int ,						_lowerCamelCase				:  int						)-> torch.Tensor:




     '''simple docstring'''





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

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

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

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







def       _UpperCamelCase			(_lowerCamelCase				:  Callable ,						_lowerCamelCase				:  Dict[str, Any] ,						_lowerCamelCase				:  int ,						_lowerCamelCase				:  int ,						_lowerCamelCase				:  bool = False ,						_lowerCamelCase				:  Any = None ,						_lowerCamelCase				:  bool = False ,						)-> Any:




     '''simple docstring'''





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

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

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

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

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

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

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

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

          __snake_case            =	tensor_tree_map(_lowerCamelCase ,						_lowerCamelCase						)

          # Run the layer on the chunk
          __snake_case            =	layer(**_lowerCamelCase						)

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

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

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

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

          i += chunk_size

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

     return out







class      lowerCAmelCase :


       def __init__( self     ,	__SCREAMING_SNAKE_CASE = 512     ,	)   ->	List[Any]:



            '''simple docstring'''




            __snake_case            =	max_chunk_size
            __snake_case            =	None
            __snake_case            =	None


       def    lowerCAmelCase   ( self     ,	__SCREAMING_SNAKE_CASE     ,	__SCREAMING_SNAKE_CASE     ,	__SCREAMING_SNAKE_CASE	)   ->	int:



            '''simple docstring'''




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

            if min_chunk_size >= self.max_chunk_size:
                 return min_chunk_size

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

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

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

            return candidates[min_viable_chunk_size_index]


       def    lowerCAmelCase   ( self     ,	__SCREAMING_SNAKE_CASE     ,	__SCREAMING_SNAKE_CASE	)   ->	bool:



            '''simple docstring'''




            __snake_case            =	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	):
                      __snake_case            =	[v for _, v in sorted(aa.items()     ,	key=lambda __SCREAMING_SNAKE_CASE	: x[0]	)]
                      __snake_case            =	[v for _, v in sorted(aa.items()     ,	key=lambda __SCREAMING_SNAKE_CASE	: x[0]	)]
                      consistent &= self._compare_arg_caches(__SCREAMING_SNAKE_CASE     ,	__SCREAMING_SNAKE_CASE	)
                 else:
                      consistent &= aa == aa

            return consistent




       def    lowerCAmelCase   ( self     ,	__SCREAMING_SNAKE_CASE     ,	__SCREAMING_SNAKE_CASE     ,	__SCREAMING_SNAKE_CASE     ,	)   ->	int:



            '''simple docstring'''




            __snake_case            =	True
            __snake_case            =	tree_map(lambda __SCREAMING_SNAKE_CASE	: 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	)
                 __snake_case            =	self._compare_arg_caches(self.cached_arg_data     ,	__SCREAMING_SNAKE_CASE	)
            else:
                 # Otherwise, we can reuse the precomputed value
                 __snake_case            =	False

            if not consistent:
                 __snake_case            =	self._determine_favorable_chunk_size(
                     __SCREAMING_SNAKE_CASE     ,	__SCREAMING_SNAKE_CASE     ,	__SCREAMING_SNAKE_CASE     ,	)
                 __snake_case            =	arg_data

            assert self.cached_chunk_size is not None

            return self.cached_chunk_size












'''simple docstring'''



def       _UpperCamelCase			(_lowerCamelCase				:  int						)-> int:




     '''simple docstring'''





     if divisor % 5 == 0 or divisor % 2 == 0:
          return 0
     __snake_case            =	1
     __snake_case            =	1
     while repunit:
          __snake_case            =	(10 * repunit + 1) % divisor
          repunit_index += 1
     return repunit_index







def       _UpperCamelCase			(_lowerCamelCase				:  int = 1_00_00_00						)-> int:




     '''simple docstring'''





     __snake_case            =	limit - 1
     if divisor % 2 == 0:
          divisor += 1
     while least_divisible_repunit(_lowerCamelCase						) <= limit:
          divisor += 2
     return divisor


if __name__ == "__main__":
     print(F"""{solution() = }""")






