



'''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 					__lowerCamelCase					(				lowerCAmelCase_      )      ->				Tuple:
		_a					:				Tuple				=							[]
		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 					__lowerCamelCase					(				lowerCAmelCase_						,      lowerCAmelCase_      )      ->				int:
		_a					:				int				=							[]
		for d in reversed(lowerCAmelCase_      ):
				idx.append(flat_idx % d      )
				_a					:				Dict				=							flat_idx // d

		return tuple(reversed(lowerCAmelCase_      )      )



@torch.jit.ignore
def 					__lowerCamelCase					(				lowerCAmelCase_						,      lowerCAmelCase_						,      lowerCAmelCase_						,      lowerCAmelCase_ = None						,      lowerCAmelCase_ = None						,      )      ->				List[str]:

		# 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(lowerCAmelCase_      ) -> None:
				_a					:				Union[str, Any]				=							True
				for i in range(len(lowerCAmelCase_      )      ):
						_a					:				int				=							-1 * (i + 1)
						l[reversed_idx] &= tally
						_a					:				Tuple				=							l[reversed_idx]

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

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

		# 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

		_a					:				Tuple[slice, ...]				=							tuple(lowerCAmelCase_      )
		_a					:				Union[str, Any]				=							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

				_a					:				int				=							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

				_a					:				Optional[Any]				=							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()      )
				_a					:				Optional[Any]				=							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					(				lowerCAmelCase_						,      lowerCAmelCase_						,      lowerCAmelCase_						,      lowerCAmelCase_      )      ->				Dict:
		_a					:				List[Any]				=							t.shape[:no_batch_dims]
		_a					:				Optional[int]				=							list(_flat_idx_to_idx(lowerCAmelCase_						,      lowerCAmelCase_      )      )
		# _get_minimal_slice_set is inclusive
		_a					:				Optional[Any]				=							list(_flat_idx_to_idx(flat_end - 1						,      lowerCAmelCase_      )      )

		# Get an ordered list of slices to perform
		_a					:				Tuple				=							_get_minimal_slice_set(
		    lowerCAmelCase_						,      lowerCAmelCase_						,      lowerCAmelCase_						,      )

		_a					:				int				=							[t[s] for s in slices]

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



def 					__lowerCamelCase					(				lowerCAmelCase_						,      lowerCAmelCase_						,      lowerCAmelCase_						,      lowerCAmelCase_						,      lowerCAmelCase_ = False						,      lowerCAmelCase_ = None						,      lowerCAmelCase_ = False						,      )      ->				Dict:
		if not (len(lowerCAmelCase_      ) > 0):
				raise ValueError('Must provide at least one input'      )

		_a					:				List[str]				=							[shape[:no_batch_dims] for shape in _fetch_dims(lowerCAmelCase_      )]
		_a					:				List[Any]				=							tuple([max(lowerCAmelCase_      ) for s in zip(*lowerCAmelCase_      )]      )

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

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

		_a					:				List[Any]				=							1
		for d in orig_batch_dims:
				flat_batch_dim *= d

		_a					:				Any				=							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

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

				_a					:				Dict[str, Any]				=							tensor_tree_map(lowerCAmelCase_						,      lowerCAmelCase_      )

				# Run the layer on the chunk
				_a					:				Any				=							layer(**lowerCAmelCase_      )

				# Allocate space for the output
				if out is None:
						_a					:				List[Any]				=							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_						,      lowerCAmelCase_      ) -> 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:
														_a					:				List[Any]				=							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:
										_a					:				Union[str, Any]				=							xa
				elif isinstance(lowerCAmelCase_						,      torch.Tensor      ):
						if _add_into_out:
								out[i : i + chunk_size] += output_chunk
						else:
								_a					:				List[Any]				=							output_chunk
				else:
						raise ValueError('Not supported'      )

				i += chunk_size

		_a					:				Optional[int]				=							tensor_tree_map(lambda lowerCAmelCase_      : t.view(orig_batch_dims + t.shape[1:]      )						,      lowerCAmelCase_      )

		return out

class      __magic_name__						:







							def __init__(							self :	Optional[int]     ,_UpperCAmelCase :	int = 512     ,):
									_a					:				Tuple				=							max_chunk_size
									_a					:				Optional[int]				=							None
									_a					:				Optional[tuple]				=							None







							def 							__lowercase      (							self :	Union[str, Any]     ,_UpperCAmelCase :	Callable     ,_UpperCAmelCase :	tuple     ,_UpperCAmelCase :	int		):
									logging.info('Tuning chunk size...'		)

									if min_chunk_size >= self.max_chunk_size:
											return min_chunk_size

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

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

									_a					:				Optional[Any]				=							0
									_a					:				Any				=							len(_a		) - 1
									while i > min_viable_chunk_size_index:
											_a					:				int				=							test_chunk_size(candidates[i]		)
											if not viable:
													_a					:				Optional[int]				=							(min_viable_chunk_size_index + i) // 2
											else:
													_a					:				List[str]				=							i
													_a					:				Union[str, Any]				=							(i + len(_a		) - 1) // 2

									return candidates[min_viable_chunk_size_index]







							def 							__lowercase      (							self :	str     ,_UpperCAmelCase :	Iterable     ,_UpperCAmelCase :	Iterable		):
									_a					:				Optional[Any]				=							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		):
													_a					:				List[str]				=							[v for _, v in sorted(aa.items()     ,key=lambda _UpperCAmelCase		: x[0]		)]
													_a					:				int				=							[v for _, v in sorted(aa.items()     ,key=lambda _UpperCAmelCase		: x[0]		)]
													consistent &= self._compare_arg_caches(_a     ,_a		)
											else:
													consistent &= aa == aa

									return consistent







							def 							__lowercase      (							self :	Optional[Any]     ,_UpperCAmelCase :	Callable     ,_UpperCAmelCase :	tuple     ,_UpperCAmelCase :	int     ,):
									_a					:				Tuple				=							True
									_a					:				tuple				=							tree_map(lambda _UpperCAmelCase		: 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		)
											_a					:				Union[str, Any]				=							self._compare_arg_caches(self.cached_arg_data     ,_a		)
									else:
											# Otherwise, we can reuse the precomputed value
											_a					:				Any				=							False

									if not consistent:
											_a					:				Dict				=							self._determine_favorable_chunk_size(
											    _a     ,_a     ,_a     ,)
											_a					:				Tuple				=							arg_data

									assert self.cached_chunk_size is not None

									return self.cached_chunk_size









'''simple docstring'''

from __future__ import annotations

from collections.abc import Iterator
from typing import Any

class      __magic_name__						:







							def __init__(							self :	Dict     ,_UpperCAmelCase :	Any		):
									_a					:				Any				=							data
									_a					:				Node | None				=							None

class      __magic_name__						:







							def __init__(							self :	Any		):
									_a					:				int				=							None
									_a					:				Optional[int]				=							None







							def __iter__(							self :	Optional[int]		):
									_a					:				List[Any]				=							self.head
									while self.head:
											yield node.data
											_a					:				str				=							node.next
											if node == self.head:
													break







							def __len__(							self :	Any		):
									return sum(1 for _ in self		)







							def __repr__(							self :	int		):
									return "->".join(str(_UpperCAmelCase		) for item in iter(self		)		)







							def 							__lowercase      (							self :	Optional[Any]     ,_UpperCAmelCase :	Any		):
									self.insert_nth(len(self		)     ,_UpperCAmelCase		)







							def 							__lowercase      (							self :	str     ,_UpperCAmelCase :	Any		):
									self.insert_nth(0     ,_UpperCAmelCase		)







							def 							__lowercase      (							self :	List[str]     ,_UpperCAmelCase :	int     ,_UpperCAmelCase :	Any		):
									if index < 0 or index > len(self		):
											raise IndexError('list index out of range.'		)
									_a					:				List[str]				=							Node(_UpperCAmelCase		)
									if self.head is None:
											_a					:				Tuple				=							new_node  # first node points itself
											_a					:				int				=							new_node
									elif index == 0:  # insert at head
											_a					:				Any				=							self.head
											_a					:				Tuple				=							new_node
									else:
											_a					:				Any				=							self.head
											for _ in range(index - 1		):
													_a					:				int				=							temp.next
											_a					:				Optional[int]				=							temp.next
											_a					:				int				=							new_node
											if index == len(self		) - 1:  # insert at tail
													_a					:				Optional[int]				=							new_node







							def 							__lowercase      (							self :	List[Any]		):
									return self.delete_nth(0		)







							def 							__lowercase      (							self :	Dict		):
									return self.delete_nth(len(self		) - 1		)







							def 							__lowercase      (							self :	Union[str, Any]     ,_UpperCAmelCase :	int = 0		):
									if not 0 <= index < len(self		):
											raise IndexError('list index out of range.'		)
									_a					:				Optional[int]				=							self.head
									if self.head == self.tail:  # just one node
											_a					:				Optional[int]				=							None
									elif index == 0:  # delete head node
											_a					:				Dict				=							self.tail.next.next
											_a					:				Dict				=							self.head.next
									else:
											_a					:				List[Any]				=							self.head
											for _ in range(index - 1		):
													_a					:				Union[str, Any]				=							temp.next
											_a					:				Optional[int]				=							temp.next
											_a					:				List[str]				=							temp.next.next
											if index == len(self		) - 1:  # delete at tail
													_a					:				int				=							temp
									return delete_node.data







							def 							__lowercase      (							self :	int		):
									return len(self		) == 0



def 					__lowerCamelCase					(				)      ->				None:
		_a					:				int				=							CircularLinkedList()
		assert len(lowerCAmelCase_  ) == 0
		assert circular_linked_list.is_empty() is True
		assert str(lowerCAmelCase_  ) == ""

		try:
				circular_linked_list.delete_front()
				raise AssertionError  # This should not happen
		except IndexError:
				assert True  # This should happen

		try:
				circular_linked_list.delete_tail()
				raise AssertionError  # This should not happen
		except IndexError:
				assert True  # This should happen

		try:
				circular_linked_list.delete_nth(-1  )
				raise AssertionError
		except IndexError:
				assert True

		try:
				circular_linked_list.delete_nth(0  )
				raise AssertionError
		except IndexError:
				assert True

		assert circular_linked_list.is_empty() is True
		for i in range(5  ):
				assert len(lowerCAmelCase_  ) == i
				circular_linked_list.insert_nth(lowerCAmelCase_						,      i + 1  )
		assert str(lowerCAmelCase_  ) == "->".join(str(lowerCAmelCase_  ) for i in range(1						,      6  )  )

		circular_linked_list.insert_tail(6  )
		assert str(lowerCAmelCase_  ) == "->".join(str(lowerCAmelCase_  ) for i in range(1						,      7  )  )
		circular_linked_list.insert_head(0  )
		assert str(lowerCAmelCase_  ) == "->".join(str(lowerCAmelCase_  ) for i in range(0						,      7  )  )

		assert circular_linked_list.delete_front() == 0
		assert circular_linked_list.delete_tail() == 6
		assert str(lowerCAmelCase_  ) == "->".join(str(lowerCAmelCase_  ) for i in range(1						,      6  )  )
		assert circular_linked_list.delete_nth(2  ) == 3

		circular_linked_list.insert_nth(2						,      3  )
		assert str(lowerCAmelCase_  ) == "->".join(str(lowerCAmelCase_  ) for i in range(1						,      6  )  )

		assert circular_linked_list.is_empty() is False


if __name__ == "__main__":
							import doctest

							doctest.testmod()




