

import tensorflow as tf

from ...tf_utils import shape_list







class 					lowercase							(			tf.keras.layers.Layer     ):




				def __init__(   self ,  snake_case ,  snake_case ,  snake_case ,  snake_case ,  snake_case=1 ,  snake_case=False ,  **snake_case			):
											super().__init__(**snake_case			)

											snake_case_     							=       vocab_size
											snake_case_     							=       d_embed
											snake_case_     							=       d_proj

											snake_case_     							=       cutoffs + [vocab_size]
											snake_case_     							=       [0] + self.cutoffs
											snake_case_     							=       div_val

											snake_case_     							=       self.cutoffs[0]
											snake_case_     							=       len(self.cutoffs			) - 1
											snake_case_     							=       self.shortlist_size + self.n_clusters
											snake_case_     							=       keep_order

											snake_case_     							=       []
											snake_case_     							=       []




				def        a				(   self ,  snake_case			):
											if self.n_clusters > 0:
																		snake_case_     							=       self.add_weight(
																		    shape=(self.n_clusters, self.d_embed) ,  initializer='zeros' ,  trainable=snake_case ,  name='cluster_weight'			)
																		snake_case_     							=       self.add_weight(
																		    shape=(self.n_clusters,) ,  initializer='zeros' ,  trainable=snake_case ,  name='cluster_bias'			)

											if self.div_val == 1:
																		for i in range(len(self.cutoffs			)			):
																									if self.d_proj != self.d_embed:
																																snake_case_     							=       self.add_weight(
																																    shape=(self.d_embed, self.d_proj) ,  initializer='zeros' ,  trainable=snake_case ,  name=F'''out_projs_._{i}''' ,  )
																																self.out_projs.append(snake_case			)
																									else:
																																self.out_projs.append(snake_case			)
																									snake_case_     							=       self.add_weight(
																									    shape=(self.vocab_size, self.d_embed) ,  initializer='zeros' ,  trainable=snake_case ,  name=F'''out_layers_._{i}_._weight''' ,  )
																									snake_case_     							=       self.add_weight(
																									    shape=(self.vocab_size,) ,  initializer='zeros' ,  trainable=snake_case ,  name=F'''out_layers_._{i}_._bias''' ,  )
																									self.out_layers.append((weight, bias)			)
											else:
																		for i in range(len(self.cutoffs			)			):
																									snake_case_							,       snake_case_     							=       self.cutoff_ends[i], self.cutoff_ends[i + 1]
																									snake_case_     							=       self.d_embed // (self.div_val**i)

																									snake_case_     							=       self.add_weight(
																									    shape=(d_emb_i, self.d_proj) ,  initializer='zeros' ,  trainable=snake_case ,  name=F'''out_projs_._{i}'''			)
																									self.out_projs.append(snake_case			)
																									snake_case_     							=       self.add_weight(
																									    shape=(r_idx - l_idx, d_emb_i) ,  initializer='zeros' ,  trainable=snake_case ,  name=F'''out_layers_._{i}_._weight''' ,  )
																									snake_case_     							=       self.add_weight(
																									    shape=(r_idx - l_idx,) ,  initializer='zeros' ,  trainable=snake_case ,  name=F'''out_layers_._{i}_._bias''' ,  )
																									self.out_layers.append((weight, bias)			)
											super().build(snake_case			)




				@staticmethod
				def        a				(   snake_case ,  snake_case ,  snake_case ,  snake_case=None			):
											snake_case_     							=       x
											if proj is not None:
																		snake_case_     							=       tf.einsum('ibd,ed->ibe' ,  snake_case ,  snake_case			)
											return tf.einsum('ibd,nd->ibn' ,  snake_case ,  snake_case			) + b




				@staticmethod
				def        a				(   snake_case ,  snake_case			):
											snake_case_     							=       shape_list(snake_case			)
											snake_case_     							=       tf.range(lp_size[0] ,  dtype=target.dtype			)
											snake_case_     							=       tf.stack([r, target] ,  1			)
											return tf.gather_nd(snake_case ,  snake_case			)




				def        a				(   self ,  snake_case ,  snake_case ,  snake_case=True ,  snake_case=False			):
											snake_case_     							=       0
											if self.n_clusters == 0:
																		snake_case_     							=       self._logit(snake_case ,  self.out_layers[0][0] ,  self.out_layers[0][1] ,  self.out_projs[0]			)
																		if target is not None:
																									snake_case_     							=       tf.nn.sparse_softmax_cross_entropy_with_logits(labels=snake_case ,  logits=snake_case			)
																		snake_case_     							=       tf.nn.log_softmax(snake_case ,  axis=-1			)
											else:
																		snake_case_     							=       shape_list(snake_case			)
																		snake_case_     							=       []
																		snake_case_     							=       tf.zeros(hidden_sizes[:2]			)
																		for i in range(len(self.cutoffs			)			):
																									snake_case_							,       snake_case_     							=       self.cutoff_ends[i], self.cutoff_ends[i + 1]
																									if target is not None:
																																snake_case_     							=       (target >= l_idx) & (target < r_idx)
																																snake_case_     							=       tf.where(snake_case			)
																																snake_case_     							=       tf.boolean_mask(snake_case ,  snake_case			) - l_idx

																									if self.div_val == 1:
																																snake_case_     							=       self.out_layers[0][0][l_idx:r_idx]
																																snake_case_     							=       self.out_layers[0][1][l_idx:r_idx]
																									else:
																																snake_case_     							=       self.out_layers[i][0]
																																snake_case_     							=       self.out_layers[i][1]

																									if i == 0:
																																snake_case_     							=       tf.concat([cur_W, self.cluster_weight] ,  0			)
																																snake_case_     							=       tf.concat([cur_b, self.cluster_bias] ,  0			)

																																snake_case_     							=       self._logit(snake_case ,  snake_case ,  snake_case ,  self.out_projs[0]			)
																																snake_case_     							=       tf.nn.log_softmax(snake_case			)
																																out.append(head_logprob[..., : self.cutoffs[0]]			)
																																if target is not None:
																																							snake_case_     							=       tf.boolean_mask(snake_case ,  snake_case			)
																																							snake_case_     							=       self._gather_logprob(snake_case ,  snake_case			)
																									else:
																																snake_case_     							=       self._logit(snake_case ,  snake_case ,  snake_case ,  self.out_projs[i]			)
																																snake_case_     							=       tf.nn.log_softmax(snake_case			)
																																snake_case_     							=       self.cutoffs[0] + i - 1  # No probability for the head cluster
																																snake_case_     							=       head_logprob[..., cluster_prob_idx, None] + tail_logprob
																																out.append(snake_case			)
																																if target is not None:
																																							snake_case_     							=       tf.boolean_mask(snake_case ,  snake_case			)
																																							snake_case_     							=       tf.boolean_mask(snake_case ,  snake_case			)
																																							snake_case_     							=       self._gather_logprob(snake_case ,  snake_case			)
																																							cur_logprob += cur_head_logprob[:, self.cutoff_ends[1] + i - 1]
																									if target is not None:
																																loss += tf.scatter_nd(snake_case ,  -cur_logprob ,  shape_list(snake_case			)			)
																		snake_case_     							=       tf.concat(snake_case ,  axis=-1			)

											if target is not None:
																		if return_mean:
																									snake_case_     							=       tf.reduce_mean(snake_case			)
																		# Add the training-time loss value to the layer using `self.add_loss()`.
																		self.add_loss(snake_case			)

																		# Log the loss as a metric (we could log arbitrary metrics,
																		# including different metrics for training and inference.
																		self.add_metric(snake_case ,  name=self.name ,  aggregation='mean' if return_mean else ''			)

											return out







_UpperCAmelCase						:  int									=			range(2, 20 + 1)
_UpperCAmelCase						:  Dict									=			[10**k for k in range(ks[-1] + 1)]
_UpperCAmelCase						:  dict[int, dict[int, list[list[int]]]]									=			{}





def    __lowerCamelCase      (					UpperCamelCase__  ,				UpperCamelCase__  ,				UpperCamelCase__  ,				UpperCamelCase__ ):







							'''simple docstring'''






							snake_case_     							=       sum(a_i[j] for j in range(UpperCamelCase__  ,				len(UpperCamelCase__ ) ) )
							snake_case_     							=       sum(a_i[j] * base[j] for j in range(min(len(UpperCamelCase__ )  ,				UpperCamelCase__ ) ) )

							snake_case_							,       snake_case_     							=       0, 0
							snake_case_     							=       n - i

							snake_case_     							=       memo.get(UpperCamelCase__ )

							if sub_memo is not None:
														snake_case_     							=       sub_memo.get(UpperCamelCase__ )

														if jumps is not None and len(UpperCamelCase__ ) > 0:
																					# find and make the largest jump without going over
																					snake_case_     							=       -1
																					for _k in range(len(UpperCamelCase__ ) - 1  ,				-1  ,				-1 ):
																												if jumps[_k][2] <= k and jumps[_k][1] <= max_dn:
																																			snake_case_     							=       _k
																																			break

																					if max_jump >= 0:
																												snake_case_							,       snake_case_							,       snake_case_     							=       jumps[max_jump]
																												# since the difference between jumps is cached, add c
																												snake_case_     							=       diff + c
																												for j in range(min(UpperCamelCase__  ,				len(UpperCamelCase__ ) ) ):
																																			snake_case_							,       snake_case_     							=       divmod(UpperCamelCase__  ,				10 )
																												if new_c > 0:
																																			add(UpperCamelCase__  ,				UpperCamelCase__  ,				UpperCamelCase__ )

														else:
																					snake_case_     							=       []
							else:
														snake_case_     							=       {c: []}
														snake_case_     							=       sub_memo

							if dn >= max_dn or c + diff >= base[k]:
														return diff, dn

							if k > ks[0]:
														while True:
																					# keep doing smaller jumps
																					snake_case_							,       snake_case_     							=       next_term(UpperCamelCase__  ,				k - 1  ,				i + dn  ,				UpperCamelCase__ )
																					diff += _diff
																					dn += terms_jumped

																					if dn >= max_dn or c + diff >= base[k]:
																												break
							else:
														# would be too small a jump, just compute sequential terms instead
														snake_case_							,       snake_case_     							=       compute(UpperCamelCase__  ,				UpperCamelCase__  ,				i + dn  ,				UpperCamelCase__ )
														diff += _diff
														dn += terms_jumped

							snake_case_     							=       sub_memo[c]

							# keep jumps sorted by # of terms skipped
							snake_case_     							=       0
							while j < len(UpperCamelCase__ ):
														if jumps[j][1] > dn:
																					break
														j += 1

							# cache the jump for this value digitsum(b) and c
							sub_memo[c].insert(UpperCamelCase__  ,				(diff, dn, k) )
							return (diff, dn)





def    __lowerCamelCase      (					UpperCamelCase__  ,				UpperCamelCase__  ,				UpperCamelCase__  ,				UpperCamelCase__ ):







							'''simple docstring'''






							if i >= n:
														return 0, i
							if k > len(UpperCamelCase__ ):
														a_i.extend([0 for _ in range(k - len(UpperCamelCase__ ) )] )

							# note: a_i -> b * 10^k + c
							# ds_b -> digitsum(b)
							# ds_c -> digitsum(c)
							snake_case_     							=       i
							snake_case_							,       snake_case_							,       snake_case_     							=       0, 0, 0
							for j in range(len(UpperCamelCase__ ) ):
														if j >= k:
																					ds_b += a_i[j]
														else:
																					ds_c += a_i[j]

							while i < n:
														i += 1
														snake_case_     							=       ds_c + ds_b
														diff += addend
														snake_case_     							=       0
														for j in range(UpperCamelCase__ ):
																					snake_case_     							=       a_i[j] + addend
																					snake_case_							,       snake_case_     							=       divmod(UpperCamelCase__  ,				10 )

																					ds_c += a_i[j]

														if addend > 0:
																					break

							if addend > 0:
														add(UpperCamelCase__  ,				UpperCamelCase__  ,				UpperCamelCase__ )
							return diff, i - start_i





def    __lowerCamelCase      (					UpperCamelCase__  ,				UpperCamelCase__  ,				UpperCamelCase__ ):







							'''simple docstring'''






							for j in range(UpperCamelCase__  ,				len(UpperCamelCase__ ) ):
														snake_case_     							=       digits[j] + addend
														if s >= 10:
																					snake_case_							,       snake_case_     							=       divmod(UpperCamelCase__  ,				10 )
																					snake_case_     							=       addend // 10 + quotient
														else:
																					snake_case_     							=       s
																					snake_case_     							=       addend // 10

														if addend == 0:
																					break

							while addend > 0:
														snake_case_							,       snake_case_     							=       divmod(UpperCamelCase__  ,				10 )
														digits.append(UpperCamelCase__ )





def    __lowerCamelCase      (					UpperCamelCase__ = 10**15 ):







							'''simple docstring'''






							snake_case_     							=       [1]
							snake_case_     							=       1
							snake_case_     							=       0
							while True:
														snake_case_							,       snake_case_     							=       next_term(UpperCamelCase__  ,				20  ,				i + dn  ,				UpperCamelCase__ )
														dn += terms_jumped
														if dn == n - i:
																					break

							snake_case_     							=       0
							for j in range(len(UpperCamelCase__ ) ):
														a_n += digits[j] * 10**j
							return a_n


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




