import torch
import torch.nn as nn
import math

class SinusoidalPositionalEncoding(nn.Module):
    def __init__(self, d_model, max_len=5000):
        super().__init__()
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        self.register_buffer('pe', pe.unsqueeze(0))

    def forward(self, x):
        return x + self.pe[:, :x.size(1)]

class EmbeddingsAndPositionalEncoding(nn.Module):
    def __init__(self, vocab_size, d_model, max_len=5000, dropout_rate=0.1):
        super().__init__()
        self.d_model = d_model
        self.token_embedding = nn.Embedding(vocab_size, d_model)
        self.positional_encoding = SinusoidalPositionalEncoding(d_model, max_len)
        self.dropout = nn.Dropout(dropout_rate)
        
    def forward(self, input_ids):
        x = self.token_embedding(input_ids)
        x = x * math.sqrt(self.d_model)
        x = self.positional_encoding(x)
        return self.dropout(x)

if __name__ == '__main__':
    VOCAB_SIZE = 10000
    D_MODEL = 512
    BATCH_SIZE = 4
    SEQ_LEN = 60
    DROPOUT = 0.1

    input_layer = EmbeddingsAndPositionalEncoding(VOCAB_SIZE, D_MODEL, dropout_rate=DROPOUT)
    dummy_input_ids = torch.randint(high=VOCAB_SIZE, size=(BATCH_SIZE, SEQ_LEN))
    output = input_layer(dummy_input_ids)
    
    print(f"入力ID形状: {dummy_input_ids.shape}")
    print(f"出力形状: {output.shape}")

    expected_shape = (BATCH_SIZE, SEQ_LEN, D_MODEL)
    assert output.shape == expected_shape
    
    print(f"\nPEテーブルの最初の値（学習されない）: {input_layer.positional_encoding.pe[0, 0, 0].item():.4f}")

# Attention Is All You Need
# 3.4 Embeddings / 3.5 Positional Encoding
# EmbeddingsAndPositionalEncoding.py
# 第2部 第4章