




"""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   _snake_case    (       lowercase__	):
				_lowerCamelCase  :    int					=					[]
				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   _snake_case    (       lowercase__							,					lowercase__	):
				_lowerCamelCase  :    List[Any]					=					[]
				for d in reversed(lowercase__	):
								idx.append(flat_idx % d	)
								_lowerCamelCase  :    Optional[Any]					=					flat_idx // d

				return tuple(reversed(lowercase__	)	)







@torch.jit.ignore
def   _snake_case    (       lowercase__							,					lowercase__							,					lowercase__							,					lowercase__ = None							,					lowercase__ = 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(lowercase__	) -> None:
								_lowerCamelCase  :    Any					=					True
								for i in range(len(lowercase__	)	):
												_lowerCamelCase  :    Optional[int]					=					-1 * (i + 1)
												l[reversed_idx] &= tally
												_lowerCamelCase  :    List[str]					=					l[reversed_idx]

				if start_edges is None:
								_lowerCamelCase  :    Optional[Any]					=					[s == 0 for s in start]
								reduce_edge_list(lowercase__	)
				if end_edges is None:
								_lowerCamelCase  :    Union[str, Any]					=					[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	),)]

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

				# 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

				_lowerCamelCase  :    Tuple[slice, ...]					=					tuple(lowercase__	)
				_lowerCamelCase  :    Tuple					=					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

								_lowerCamelCase  :    Optional[int]					=					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

								_lowerCamelCase  :    Union[str, Any]					=					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()	)
								_lowerCamelCase  :    int					=					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   _snake_case    (       lowercase__							,					lowercase__							,					lowercase__							,					lowercase__	):
				_lowerCamelCase  :    Optional[int]					=					t.shape[:no_batch_dims]
				_lowerCamelCase  :    Any					=					list(_flat_idx_to_idx(lowercase__							,					lowercase__	)	)
				# _get_minimal_slice_set is inclusive
				_lowerCamelCase  :    Any					=					list(_flat_idx_to_idx(flat_end - 1							,					lowercase__	)	)

				# Get an ordered list of slices to perform
				_lowerCamelCase  :    int					=					_get_minimal_slice_set(
				    lowercase__							,					lowercase__							,					lowercase__							,					)

				_lowerCamelCase  :    List[str]					=					[t[s] for s in slices]

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







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

				_lowerCamelCase  :    Union[str, Any]					=					[shape[:no_batch_dims] for shape in _fetch_dims(lowercase__	)]
				_lowerCamelCase  :    Optional[int]					=					tuple([max(lowercase__	) for s in zip(*lowercase__	)]	)

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

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

				_lowerCamelCase  :    Optional[int]					=					1
				for d in orig_batch_dims:
								flat_batch_dim *= d

				_lowerCamelCase  :    Any					=					flat_batch_dim // chunk_size + (flat_batch_dim % chunk_size != 0)

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

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

								_lowerCamelCase  :    Dict[str, Any]					=					tensor_tree_map(lowercase__							,					lowercase__	)

								# Run the layer on the chunk
								_lowerCamelCase  :    Union[str, Any]					=					layer(**lowercase__	)

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

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

												def assign(lowercase__							,					lowercase__	) -> 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:
																												_lowerCamelCase  :    int					=					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:
																				_lowerCamelCase  :    str					=					xa
								elif isinstance(lowercase__							,					torch.Tensor	):
												if _add_into_out:
																out[i : i + chunk_size] += output_chunk
												else:
																_lowerCamelCase  :    int					=					output_chunk
								else:
												raise ValueError('Not supported'	)

								i += chunk_size

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

				return out






class lowerCAmelCase__			:






			'''simple docstring'''



			def __init__(						self						,   lowercase = 512						,   ):
							_lowerCamelCase  :    int					=					max_chunk_size
							_lowerCamelCase  :    Optional[int]					=					None
							_lowerCamelCase  :    Optional[tuple]					=					None
			def       A_					(						self						,   lowercase						,   lowercase						,   lowercase						):
							logging.info('Tuning chunk size...'						)

							if min_chunk_size >= self.max_chunk_size:
											return min_chunk_size

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

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

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

							return candidates[min_viable_chunk_size_index]
			def       A_					(						self						,   lowercase						,   lowercase						):
							_lowerCamelCase  :    int					=					True
							for aa, aa in zip(lowercase						,   lowercase						):
											assert type(lowercase						) == type(lowercase						)
											if isinstance(lowercase						,   (list, tuple)						):
															consistent &= self._compare_arg_caches(lowercase						,   lowercase						)
											elif isinstance(lowercase						,   lowercase						):
															_lowerCamelCase  :    Any					=					[v for _, v in sorted(aa.items()						,   key=lambda lowercase						: x[0]						)]
															_lowerCamelCase  :    Optional[Any]					=					[v for _, v in sorted(aa.items()						,   key=lambda lowercase						: x[0]						)]
															consistent &= self._compare_arg_caches(lowercase						,   lowercase						)
											else:
															consistent &= aa == aa

							return consistent






			def       A_					(						self						,   lowercase						,   lowercase						,   lowercase						,   ):
							_lowerCamelCase  :    List[Any]					=					True
							_lowerCamelCase  :    tuple					=					tree_map(lambda lowercase						: a.shape if isinstance(lowercase						,   torch.Tensor						) else a						,   lowercase						,   lowercase						)
							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(lowercase						)
											_lowerCamelCase  :    Optional[Any]					=					self._compare_arg_caches(self.cached_arg_data						,   lowercase						)
							else:
											# Otherwise, we can reuse the precomputed value
											_lowerCamelCase  :    List[str]					=					False

							if not consistent:
											_lowerCamelCase  :    List[Any]					=					self._determine_favorable_chunk_size(
											    lowercase						,   lowercase						,   lowercase						,   )
											_lowerCamelCase  :    List[Any]					=					arg_data

							assert self.cached_chunk_size is not None

							return self.cached_chunk_size





"""simple docstring"""






def   _snake_case    (       lowercase__ = 1000	):
				_lowerCamelCase,				_lowerCamelCase  :    Optional[int]					=					1, 1
				_lowerCamelCase  :    int					=					[]
				for i in range(1							,					n + 1	):
								_lowerCamelCase  :    Tuple					=					prev_numerator + 2 * prev_denominator
								_lowerCamelCase  :    str					=					prev_numerator + prev_denominator
								if len(str(lowercase__	)	) > len(str(lowercase__	)	):
												result.append(lowercase__	)
								_lowerCamelCase  :    Dict					=					numerator
								_lowerCamelCase  :    Optional[int]					=					denominator

				return len(lowercase__	)


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