from __future__ import annotations

from collections import defaultdict
from typing import Optional

import gymnasium as gym
import numpy as np
import pandas as pd

__all__ = [
    "montecarlo",
    "montecarlo_code",
    "td_prediction",
    "sarsa",
    "q_learning",
]

__version__ = "1.1.0"


def _epsilon_greedy_action(
    q_values: np.ndarray,
    epsilon: float,
    rng: np.random.Generator,
) -> int:
    if rng.random() < epsilon:
        return int(rng.integers(len(q_values)))
    return int(np.argmax(q_values))


def _q_table_to_frame(q_table: np.ndarray) -> pd.DataFrame:
    n_states, n_actions = q_table.shape
    rows = [
        {"state": s, "action": a, "value": float(q_table[s, a])}
        for s in range(n_states)
        for a in range(n_actions)
    ]
    return pd.DataFrame(rows)


def montecarlo_code(
    num_episodes: int = 100,
    epsilon: float = 0.5,
    seed: Optional[int] = None,
) -> pd.DataFrame:
    """Run first-visit Monte Carlo control on Blackjack-v1."""
    if num_episodes <= 0:
        return pd.DataFrame(columns=["state", "action", "value"])

    env = gym.make("Blackjack-v1")
    rng = np.random.default_rng(seed)

    q_table = defaultdict(float)
    returns_sum = defaultdict(float)
    returns_count = defaultdict(int)

    def epsilon_greedy_action(state) -> int:
        if rng.random() < epsilon:
            return env.action_space.sample()
        q_hit = q_table[(state, 1)]
        q_stick = q_table[(state, 0)]
        if q_hit == q_stick:
            return env.action_space.sample()
        return 1 if q_hit > q_stick else 0

    state, _ = env.reset(seed=seed)
    if seed is not None:
        env.action_space.seed(seed)
    for episode in range(num_episodes):
        if episode > 0:
            state, _ = env.reset()

        episode_steps = []
        terminated = False
        truncated = False
        while not (terminated or truncated):
            action = epsilon_greedy_action(state)
            next_state, reward, terminated, truncated, _ = env.step(action)
            episode_steps.append((state, action, reward))
            state = next_state

        visited = set()
        g_return = 0.0
        for state_t, action_t, reward_t in reversed(episode_steps):
            g_return = reward_t + g_return
            if (state_t, action_t) in visited:
                continue
            visited.add((state_t, action_t))
            returns_sum[(state_t, action_t)] += g_return
            returns_count[(state_t, action_t)] += 1
            q_table[(state_t, action_t)] = (
                returns_sum[(state_t, action_t)] / returns_count[(state_t, action_t)]
            )

    env.close()
    rows = [
        {"state": state_key, "action": action_key, "value": float(value)}
        for (state_key, action_key), value in q_table.items()
    ]
    return pd.DataFrame(rows)


def td_prediction(
    num_episodes: int = 50,
    max_steps: int = 10,
    alpha: float = 0.85,
    gamma: float = 0.90,
    seed: Optional[int] = None,
) -> pd.DataFrame:
    """Estimate state values on FrozenLake-v1 using TD(0)."""
    env = gym.make("FrozenLake-v1")
    if seed is not None:
        env.reset(seed=seed)
        env.action_space.seed(seed)

    v_table = np.zeros(env.observation_space.n, dtype=float)

    for _ in range(num_episodes):
        state, _ = env.reset()
        for _ in range(max_steps):
            action = env.action_space.sample()
            next_state, reward, terminated, truncated, _ = env.step(action)
            v_table[state] += alpha * (
                reward + gamma * v_table[next_state] - v_table[state]
            )
            if terminated or truncated:
                break
            state = next_state

    env.close()
    return pd.DataFrame(
        {"state": np.arange(len(v_table), dtype=int), "value": v_table}
    )


def sarsa(
    num_episodes: int = 500,
    max_steps: int = 50,
    alpha: float = 0.85,
    gamma: float = 0.90,
    epsilon: float = 0.8,
    seed: Optional[int] = None,
) -> pd.DataFrame:
    """Train a SARSA agent on FrozenLake-v1."""
    env = gym.make("FrozenLake-v1")
    rng = np.random.default_rng(seed)
    if seed is not None:
        env.reset(seed=seed)
        env.action_space.seed(seed)

    n_states = env.observation_space.n
    n_actions = env.action_space.n
    q_table = np.zeros((n_states, n_actions), dtype=float)

    for _ in range(num_episodes):
        state, _ = env.reset()
        action = _epsilon_greedy_action(q_table[state], epsilon, rng)
        for _ in range(max_steps):
            next_state, reward, terminated, truncated, _ = env.step(action)
            next_action = _epsilon_greedy_action(q_table[next_state], epsilon, rng)
            td_target = reward + gamma * q_table[next_state, next_action]
            q_table[state, action] += alpha * (td_target - q_table[state, action])
            if terminated or truncated:
                break
            state, action = next_state, next_action

    env.close()
    return _q_table_to_frame(q_table)


def q_learning(
    num_episodes: int = 200,
    max_steps: int = 100,
    alpha: float = 0.85,
    gamma: float = 0.90,
    epsilon: float = 0.8,
    seed: Optional[int] = None,
) -> pd.DataFrame:
    """Train a Q-learning agent on FrozenLake-v1."""
    env = gym.make("FrozenLake-v1")
    rng = np.random.default_rng(seed)
    if seed is not None:
        env.reset(seed=seed)
        env.action_space.seed(seed)

    n_states = env.observation_space.n
    n_actions = env.action_space.n
    q_table = np.zeros((n_states, n_actions), dtype=float)

    for _ in range(num_episodes):
        state, _ = env.reset()
        for _ in range(max_steps):
            action = _epsilon_greedy_action(q_table[state], epsilon, rng)
            next_state, reward, terminated, truncated, _ = env.step(action)
            td_target = reward + gamma * np.max(q_table[next_state])
            q_table[state, action] += alpha * (td_target - q_table[state, action])
            if terminated or truncated:
                break
            state = next_state

    env.close()
    return _q_table_to_frame(q_table)


def montecarlo():
    """Prints and returns the source code for a Monte Carlo control demo."""
    code_to_print = """
# FrozenLake Environment
    Labsheet 1 and 2
import gymnasium as gym
import time

# Create Environment
env = gym.make(
    "FrozenLake-v1",
    render_mode="human"
)

# Reset and Render
env.reset()
env.render()

time.sleep(2)

# Print State Space
print(env.observation_space)

# Print Action Space
print(env.action_space)

# Print Transition Probabilities
print(env.unwrapped.P[0][2])

# Deterministic Action
print("Deterministic Action:")

next_state, reward, done, trans_prob, info = env.step(2)

print(
    "Next state is : {0}, Reward is {1}, Is a Terminal state: {2}".format(
        next_state,
        reward,
        done
    )
)

env.render()

time.sleep(2)

# Reset Environment
env.reset()

# Random Action
print("Random Action:")

rnd_action = env.action_space.sample()

next_state, reward, done, trans_prob, info = env.step(rnd_action)

print(
    "Next state is : {0}, Reward is {1}, Is a Terminal state: {2}".format(
        next_state,
        reward,
        done
    )
)

env.render()

time.sleep(2)

# Random Episode
env.reset()

num_timesteps = 20

for t in range(num_timesteps):

    rnd_action = env.action_space.sample()

    next_state, reward, done, info, trans_prob = env.step(rnd_action)

    print(
        "Time:{0}\tNext:{1}\tReward:{2}\tTerminal?:{3}".format(
            t+1,
            next_state,
            reward,
            done
        )
    )

    env.render()

    time.sleep(1)

    if done:
        break

env.close()



# CartPole Environment

import gymnasium as gym

# Create Environment
env = gym.make(
    "CartPole-v1",
    render_mode="human"
)

# Reset and Render
env.reset()
env.render()

# Print Observation Space
print(env.observation_space)

# Print Action Space
print(env.action_space)

# Reset Environment
env.reset()

# Parameters
n_episodes = 50
n_timesteps = 50

# Random Policy
for i in range(n_episodes):

    Return = 0

    for t in range(n_timesteps):

        env.render()

        rnd_action = env.action_space.sample()

        next_state, reward, done, info, prob = env.step(rnd_action)

        Return = Return + reward

        if done:

            env.reset()

            break

    if i % 10 == 0:

        print(
            "Episode : {}, Return : {}".format(
                i+1,
                Return
            )
        )

env.close()


# Value Iteration

import gymnasium as gym
import numpy as np

# Value Iteration Function
def value_iteration(env):

    num_itns = 1000

    threshold = 1e-20

    gamma = 1.0

    value_table = np.zeros(
        env.observation_space.n
    )

    for i in range(num_itns):

        updated_val_tab = np.copy(value_table)

        for s in range(env.observation_space.n):

            Q_values = [

                sum([

                    prob * (

                        r + gamma * updated_val_tab[s_]

                    )

                    for prob, s_, r, _ in env.P[s][a]

                ])

                for a in range(env.action_space.n)

            ]

            value_table[s] = max(Q_values)

        if (

            np.sum(
                np.fabs(
                    updated_val_tab - value_table
                )
            ) <= threshold

        ):
            break

    return value_table

# Extract Policy
def extract_policy(value_table):

    gamma = 1.0

    policy = np.zeros(
        env.observation_space.n
    )

    for s in range(env.observation_space.n):

        Q_values = [

            sum([

                prob * (

                    r + gamma * value_table[s_]

                )

                for prob, s_, r, _ in env.P[s][a]

            ])

            for a in range(env.action_space.n)

        ]

        policy[s] = np.argmax(np.array(Q_values))

    return policy

# Main Program
env = gym.make(
    'FrozenLake-v1',
    render_mode='human'
)

env.reset()

env.render()

env = env.unwrapped

optimal_value_function = value_iteration(env)

optimal_policy = extract_policy(
    optimal_value_function
)

print(optimal_policy)


# Policy Iteration
Labsheet 3
import gymnasium as gym
import numpy as np

# Compute Value Function
def compute_value_function(policy):

    num_iterations = 1000

    threshold = 1e-20

    gamma = 1.0

    value_table = np.zeros(
        env.observation_space.n
    )

    for i in range(num_iterations):

        updated_value_table = np.copy(value_table)

        for s in range(env.observation_space.n):

            a = policy[s]

            value_table[s] = sum([

                prob * (

                    r + gamma * updated_value_table[s_]

                )

                for prob, s_, r, _ in env.P[s][a]

            ])

        if (

            np.sum(

                np.fabs(
                    updated_value_table - value_table
                )

            ) <= threshold

        ):
            break

    return value_table

# Extract Policy
def extract_policy(value_table):

    gamma = 1.0

    policy = np.zeros(
        env.observation_space.n
    )

    for s in range(env.observation_space.n):

        Q_values = [

            sum([

                prob * (

                    r + gamma * value_table[s_]

                )

                for prob, s_, r, _ in env.P[s][a]

            ])

            for a in range(env.action_space.n)

        ]

        policy[s] = np.argmax(np.array(Q_values))

    return policy

# Policy Iteration
def policy_iteration(env):

    num_iterations = 1000

    policy = np.zeros(
        env.observation_space.n
    )

    for i in range(num_iterations):

        value_function = compute_value_function(policy)

        new_policy = extract_policy(value_function)

        if np.all(policy == new_policy):

            break

        policy = new_policy

    return policy

# Main Program
env = gym.make(
    'FrozenLake-v1',
    render_mode='human'
)

env.reset()

env.render()

env = env.unwrapped

optimal_policy = policy_iteration(env)

print(optimal_policy)

# Run Game Using Optimal Policy
state = env.reset()

s = state[0]

done = False

tot_reward = 0

while not done:

    next_state, reward, done, _, _ = env.step(
        int(optimal_policy[s])
    )

    env.render()

    s = next_state

    tot_reward += reward

    if done:
        break

print("Return =", tot_reward)

# Every-Visit Monte Carlo Prediction

import pandas as pd

from collections import defaultdict

import gymnasium as gym

# Create Environment
env = gym.make(
    'Blackjack-v1',
    render_mode='human'
)

env.reset()

env.render()

# Policy
def policy(state):

    return 0 if state[0] > 19 else 1

state = env.reset()

state = state[0]

print(state)

print(policy(state))

# Number of Timesteps
num_timesteps = 100

# Generate Episode
def generate_episode(policy):

    episode = []

    state = env.reset()

    state = state[0]

    for t in range(num_timesteps):

        action = policy(state)

        next_state, reward, done, info, trans_prob = env.step(action)

        episode.append((state, action, reward))

        if done:
            break

        state = next_state

    return episode

print(generate_episode(policy))

# Monte Carlo Prediction Labsheet 5 and 6
total_return = defaultdict(float)

N = defaultdict(int)

num_iterations = 10

for i in range(num_iterations):

    episode = generate_episode(policy)

    states, actions, rewards = zip(*episode)

    for t, state in enumerate(states):

        R = sum(rewards[t:])

        total_return[state] += R

        N[state] += 1

print(total_return[state])

print(N[state])

# DataFrames
total_return = pd.DataFrame(

    total_return.items(),

    columns=['state', 'total_return']

)

N = pd.DataFrame(

    N.items(),

    columns=['state', 'N']

)

df = pd.merge(total_return, N, on="state")

print(df.head(10))

df['value'] = df['total_return'] / df['N']

print(df.head(10))

print(df.shape)

print(
    df[df['state'] == (21,9,False)]['value'].values
)

print(
    df[df['state'] == (5,8,False)]['value'].values
)

env.close()


#Labsheet 7
# Implementation of On-Policy Monte Carlo Control

import gym
import pandas as pd
import random

from collections import defaultdict

# Create Environment
env = gym.make('Blackjack-v1', render_mode='human')

# Initialize Q-table
Q = defaultdict(float)

total_return = defaultdict(float)

N = defaultdict(int)

# Epsilon-Greedy Policy
def epsilon_greedy_policy(state, Q):

    epsilon = 0.5

    if random.uniform(0,1) < epsilon:

        return env.action_space.sample()

    else:

        return max(

            list(range(env.action_space.n)),

            key=lambda x: Q[(state,x)]

        )

# Number of timesteps
num_timesteps = 100

# Generate Episode
def generate_episode(Q):

    episode = []

    state = env.reset()
    state = state[0]

    for t in range(num_timesteps):

        action = epsilon_greedy_policy(state, Q)

        next_state, reward, done, info, trans_prob = env.step(action)

        episode.append((state, action, reward))

        if done:
            break

        state = next_state

    return episode

# Training
num_iterations = 100

for i in range(num_iterations):

    episode = generate_episode(Q)

    all_state_action_pairs = [

        (s,a)

        for (s,a,r) in episode
    ]

    rewards = [

        r

        for (s,a,r) in episode
    ]

    for t, (state, action, _) in enumerate(episode):

        if not (state, action) in all_state_action_pairs[0:t]:

            R = sum(rewards[t:])

            total_return[(state,action)] += R

            N[(state,action)] += 1

            Q[(state,action)] = (

                total_return[(state,action)]

                /

                N[(state,action)]

            )

# Display Q-table
df = pd.DataFrame(

    Q.items(),

    columns=['state_action pair', 'value']

)

print(df.head(11))

print(df[124:126])

env.close()


#Labsheet 8
# TD Prediction

import gymnasium as gym
import pandas as pd

# Create Environment
env = gym.make("FrozenLake-v1", render_mode="human")

env.reset()
env.render()

# Random Policy
def random_policy():

    return env.action_space.sample()

# Initialize V-table
V = {}

for s in range(env.observation_space.n):

    V[s] = 0.0

# Hyperparameters
alpha = 0.85
gamma = 0.90

num_eps = 50
num_steps = 10

# Training Loop
for i in range(num_eps):

    s = env.reset()
    s = s[0]

    for t in range(num_steps):

        a = random_policy()

        s_, r, done, _, _ = env.step(a)

        # TD Update
        V[s] += alpha * (

            r + gamma * V[s_] - V[s]

        )

        s = s_

        if done:
            break

# Display Values
df = pd.DataFrame(

    list(V.items()),

    columns=['state', 'value']

)

print(df)

#LABSHEET 9
# SARSA Algorithm

import gymnasium as gym
import random
import pandas as pd

# Create Environment
env = gym.make("FrozenLake-v1", render_mode="human")

env.reset()
env.render()

# Initialize Q-table
Q = {}

for s in range(env.observation_space.n):

    for a in range(env.action_space.n):

        Q[(s,a)] = 0.0

print("Initial Q Table:", Q)

# Epsilon-Greedy Policy
def epsilon_greedy(state, epsilon):

    if random.uniform(0,1) < epsilon:

        return env.action_space.sample()

    else:

        return max(

            list(range(env.action_space.n)),

            key=lambda x: Q[(state,x)]

        )

# Hyperparameters
alpha = 0.85
gamma = 0.90
epsilon = 0.8

num_eps = 500
num_steps = 50

# Training Loop
for i in range(num_eps):

    s = env.reset()
    s = s[0]

    a = epsilon_greedy(s, epsilon)

    for t in range(num_steps):

        s_, r, done, _, _ = env.step(a)

        a_ = epsilon_greedy(s_, epsilon)

        predict = Q[(s,a)]

        target = r + gamma * Q[(s_,a_)]

        Q[(s,a)] = Q[(s,a)] + alpha * (

            target - predict

        )

        s = s_
        a = a_

        if done:
            break

# Display Q-table
df = pd.DataFrame(

    list(Q.items()),

    columns=['state_action', 'value']

)

print(df)


$LABSHEET 10
# Q-Learning Algorithm

import gymnasium as gym
import numpy as np
import random
import pandas as pd

# Create Environment
env = gym.make("FrozenLake-v1", render_mode="human")

# Initialize Q-table
Q = {}

for s in range(env.observation_space.n):

    for a in range(env.action_space.n):

        Q[(s,a)] = 0.0

# Epsilon-Greedy Policy
def epsilon_greedy(state, epsilon):

    if random.uniform(0,1) < epsilon:

        return env.action_space.sample()

    else:

        return max(

            list(range(env.action_space.n)),

            key=lambda x: Q[(state,x)]

        )

# Hyperparameters
alpha = 0.85
gamma = 0.90
epsilon = 0.8

num_eps = 50
num_steps = 100

# Training Loop
for i in range(num_eps):

    s = env.reset()
    s = s[0]

    for t in range(num_steps):

        a = epsilon_greedy(s, epsilon)

        s_, r, done, _, _ = env.step(a)

        a_ = np.argmax(

            [Q[(s_,a)] for a in range(env.action_space.n)]

        )

        Q[(s,a)] += alpha * (

            r + gamma * Q[(s_,a_)] - Q[(s,a)]

        )

        s = s_

        if done:
            break

# Display Q-table
df = pd.DataFrame(

    list(Q.items()),

    columns=['state_action', 'value']

)

print(df)


#LABSHEET 11
# Epsilon-Greedy Multi-Armed Bandit

import gym
import gym_bandits
import numpy as np

# Create Environment
env = gym.make("BanditTwoArmedHighLowFixed-v0")

print(env.action_space.n)
print(env.p_dist)

# Initialize Arrays
count = np.zeros(2)

sum_rewards = np.zeros(2)

Q = np.zeros(2)

num_rounds = 100

# Epsilon-Greedy Policy
def epsilon_greedy(epsilon):

    if np.random.uniform(0,1) < epsilon:

        return env.action_space.sample()

    else:

        return np.argmax(Q)

# Training
env.reset()

for i in range(num_rounds):

    arm = epsilon_greedy(epsilon=0.5)

    next_state, reward, done, info = env.step(arm)

    count[arm] += 1

    sum_rewards[arm] += reward

    Q[arm] = sum_rewards[arm] / count[arm]

# Display Results
print(Q)

print(

    'The optimal arm is arm {}'.format(

        np.argmax(Q)+1

    )

)


LABSHEET 12

# Softmax Exploration

import gym
import gym_bandits
import numpy as np

# Create Environment
env = gym.make("BanditTwoArmedHighLowFixed-v0")

# Initialize Arrays
count = np.zeros(2)

sum_rewards = np.zeros(2)

Q = np.zeros(2)

num_rounds = 100

# Softmax Policy
def softmax(T):

    denom = sum(

        [np.exp(i/T) for i in Q]

    )

    probs = [

        np.exp(i/T)/denom

        for i in Q
    ]

    arm = np.random.choice(

        env.action_space.n,

        p=probs

    )

    return arm

# Training
env.reset()

T = 50

for i in range(num_rounds):

    arm = softmax(T)

    next_state, reward, done, info = env.step(arm)

    count[arm] += 1

    sum_rewards[arm] += reward

    Q[arm] = sum_rewards[arm] / count[arm]

    T = T * 0.99

# Display Results
print(Q)

print(

    'The optimal arm is arm {}'.format(

        np.argmax(Q)+1

    )

)


#LABSHEET 13
# Upper Confidence Bound (UCB)

import gym
import gym_bandits
import numpy as np

# Create Environment
env = gym.make("BanditTwoArmedHighLowFixed-v0")

# Initialize Arrays
count = np.zeros(2)

sum_rewards = np.zeros(2)

Q = np.zeros(2)

num_rounds = 100

# UCB Policy
def UCB(i):

    ucb = np.zeros(2)

    if i < 2:

        return i

    else:

        for arm in range(2):

            ucb[arm] = (

                Q[arm]

                +

                np.sqrt(

                    (2*np.log(sum(count)))

                    /

                    count[arm]

                )

            )

    return np.argmax(ucb)

# Training
env.reset()

for i in range(num_rounds):

    arm = UCB(i)

    next_state, reward, done, info = env.step(arm)

    count[arm] += 1

    sum_rewards[arm] += reward

    Q[arm] = sum_rewards[arm] / count[arm]

# Display Results
print(Q)

print(

    'The optimal arm is arm {}'.format(

        np.argmax(Q)+1

    )

)


#LABSHEET 14
# Thompson Sampling

import gym
import gym_bandits
import numpy as np

# Create Environment
env = gym.make("BanditTwoArmedHighLowFixed-v0")

# Initialize Arrays
count = np.zeros(2)

sum_rewards = np.zeros(2)

Q = np.zeros(2)

alpha = np.ones(2)

beta = np.ones(2)

num_rounds = 100

# Thompson Sampling Policy
def thompson_sampling(alpha, beta):

    samples = [

        np.random.beta(

            alpha[i]+1,

            beta[i]+1

        )

        for i in range(2)
    ]

    return np.argmax(samples)

# Training
env.reset()

for i in range(num_rounds):

    arm = thompson_sampling(alpha, beta)

    next_state, reward, done, info = env.step(arm)

    count[arm] += 1

    sum_rewards[arm] += reward

    Q[arm] = sum_rewards[arm] / count[arm]

    if reward == 1:

        alpha[arm] += 1

    else:

        beta[arm] += 1

# Display Results
print(Q)

print(

    'The optimal arm is arm {}'.format(

        np.argmax(Q)+1

    )

)


LABSHEET 15
# Banner Advertisement Selection

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

plt.style.use('ggplot')

# Create Dataset
df = pd.DataFrame()

for i in range(5):

    df['Banner_type_'+str(i)] = np.random.randint(0,2,100000)

# Initialize Variables
num_iterations = 100000

num_banner = 5

count = np.zeros(num_banner)

sum_rewards = np.zeros(num_banner)

Q = np.zeros(num_banner)

banner_selected = []

# Epsilon-Greedy Policy
def epsilon_greedy_policy(epsilon):

    if np.random.uniform(0,1) < epsilon:

        return np.random.choice(num_banner)

    else:

        return np.argmax(Q)

# Training Loop
for i in range(num_iterations):

    banner = epsilon_greedy_policy(0.5)

    reward = df.values[i, banner]

    count[banner] += 1

    sum_rewards[banner] += reward

    Q[banner] = sum_rewards[banner] / count[banner]

    banner_selected.append(banner)

# Display Best Banner
print(

    'The best banner is banner {}'.format(

        np.argmax(Q)+1

    )

)

# Visualization
ax = sns.countplot(x=banner_selected)

ax.set(

    xlabel='Banner',

    ylabel='Count'

)

plt.show()
    """
    print(code_to_print)
    return code_to_print

def show_example_code():
    """Prints the source code for a sentiment analysis demo."""
    code_to_print = """


# ===== NLP_lab01(1) =====

!pip install nltk spacy gensim transformers torch sentencepiece
!python -m spacy download en_core_web_sm

import spacy
nlp = spacy.load("en_core_web_sm")
text = "Nutral language processing is fascinating"
doc = nlp(text)
for token in doc :
    print(token,text,token.vector.shape)

#Word2Vec using gensim:
from gensim.models import Word2Vec
sentences = [
    ["machine","learning",'is','fun'],
    ['natural','language','processing'],
    ['deep','learning','models']
]
model = Word2Vec(sentences,vector_size = 50 , window = 3,min_count =1)
vector = model.wv['models']
print("Vector for 'models':", vector)

#Word Similarity Using Word2Vec:
similarity = model.wv.similarity('learning','machine')
print("Similarity:",similarity)

#Glove Embeddings(Pre-trained)
import gensim.downloader as api
glove = api.load("glove-wiki-gigaword-50")
glove = api.load("glove-wiki-gigaword-50")
print(glove["learning"])
print(glove.similarity("deep","learning"))

#Contextual Embeddings using Bert
from transformers import BertTokenizer, BertModel
import torch
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
model = BertModel.from_pretrained("bert-base-uncased")
sentence = "The bank is near the river"
inputs = tokenizer(sentence,return_tensors = "pt")

outputs = model(**inputs)
embeddings = outputs.last_hidden_state
print(embeddings.shape)

#COntext Change Example
sent1 = "Ideposited money in the bank"
sent2 = "The river bank is beautiful"

inputs1 = tokenizer(sent1,return_tensors = "pt")
inputs2 = tokenizer(sent2,return_tensors = "pt")
emb1 = model(**inputs1).last_hidden_state
emb2 = model(**inputs2).last_hidden_state

#Roberta Embedding
from transformers import RobertaTokenizer, RobertaModel
tokenizer = RobertaTokenizer.from_pretrained("roberta-base")
model = RobertaModel.from_pretrained("roberta-base")
inputs = tokenizer("Word representations are powerful",
return_tensors = "pt")
output = model(**inputs)
print(outputs.last_hidden_state.shape)



# ===== NLP_lab02 =====

!pip install -U scikit-learn
#/Applications/Python\ 3.13/Install\ Certificates.command

import ssl
import nltk
from nltk.corpus import brown
from collections import Counter
nltk.download("brown")

words = brown.words()
freq_dist = Counter(word.lower() for word in words)
def is_complex(word , threshold = 50) :
    return freq_dist[word.lower()]<threshold

sentence = ('Photosynthesis is an essential biochemical process').split()

for word in sentence : 
    if is_complex(word) : 
        print(word, "-> Complex")
    else :
        print(word, "-> Simple")

import pandas as pd
from sklearn.tree import DecisionTreeClassifier
#Feature Extraction for ML Based CWI

def extract_features(word) :
    return {
        "length" : len(word),
        "frequency" : freq_dist[word.lower()]
    }

#Creating dataset
data = [("dog" , 0),("cat",0),("photosynthesis",1),("biochemical",1),("run",0),("photosynthetic",1)]

df = pd.DataFrame(data, columns = ["word" , "label"])

#Feature Extraction
features = df["word"].apply(extract_features)
X = pd.DataFrame(features.tolist())
y = df["label"]

#Train A ML model
model = DecisionTreeClassifier()
model.fit(X,y)

#Predicting Complex Words
test_words = ["plant" , "metamophosis"]
test_features = pd.DataFrame([extract_features(w) for w in test_words])
predictions = model.predict(test_features)
for word, label in zip(test_words,predictions) :
    print(word, "-> complex" if label == 1 else "-> simple")



# ===== NLP_lab03 =====

!pip install textblob nltk spacy
!python -m spacy download en_core_web_sm

#Sentiment Analysis
from textblob import TextBlob
sentences = [
    "The movie was fantastic",
    "The plot was boring",
    "The music was average",
    "I would not recommend this file",
    "The acting was excellent"
]
polarities = []

for sentence in sentences :
    blob = TextBlob(sentence)
    polarity = blob.sentiment.polarity
    polarities.append(polarity)

    if polarity > 0 :
        sentiment = "Positive"
    elif polarity < 0 :
        sentiment = "Negative"
    else :
        sentiment = "Neutral"
    print(sentence)
    print("Polarity:",polarity,"Sentiment:",sentiment)
    print()

#Named Entity Recognition
import spacy
nlp = spacy.load("en_core_web_sm")
text = "Elon Musk is the CEO of Tesla and SpaceX based in the United States"
doc = nlp(text)

for ent in doc.ents :
    print(ent.text,"->",ent.label_)

#Entity Frequency Analysis
entity_count = {}
for ent in doc.ents :
    entity_count[ent.label_] = entity_count.get(ent.label_ , 0) + 1
print("Entity Count :",entity_count)



# ===== NLP_lab04 =====

!pip install nltk spacy gensim
!python -m spacy download en_core_web_sm

import nltk
import spacy
import gensim.downloader as api
from nltk.corpus import wordnet as wn
from sklearn.metrics.pairwise import cosine_similarity
nltk.download('wordnet')
nlp = spacy.load("en_core_web_sm")
vectors = api.load("glove-wiki-gigaword-50")

def simplify(sentence) :
    words = sentence.split()
    out = []
    for w in words :
        best = w
        best_sim = 0

        if w in vectors and len(w) > 6 :
            for syn in wn.synsets(w): #Shortform of set of synonyms
                for l in syn.lemmas() :
                    s = l.name().replace("_"," ")
                    if s in vectors and len(s) < len(w) :
                        sim = cosine_similarity(
                            vectors[w].reshape(1,-1),
                            vectors[s].reshape(1,-1))[0][0]

                        if sim > best_sim : 
                            best = s
                            best_sim = sim

        out.append(best)
    return " ".join(out)

s = "The physician administered medication to alleviate symptoms"
print(simplify(s))



# ===== NLP_lab05 =====

!pip install transformers datasets sentencepiece torch

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("xlm-roberta-base")

sentences = ["Natural Language Processing is interesting.",
             "প্রাকৃতিক ভাষা প্রক্রিয়াকরণ আকর্ষণীয়।",
             "प्राकृतिक भाषा प्रसंस्करण रोचक है।",
             "Le traitement automatique du langage naturel est intéressant."
            ]

for sentence in sentences :
    tokens = tokenizer.tokenize(sentence)
    print("\nSentence:",sentence)
    print("Tokens:",tokens)

from transformers import pipeline
translator = pipeline(
    model = "Helsinki-NLP/opus-mt-hi-en"
)

sentences = ["Natural Language Processing is interesting.",
             "প্রাকৃতিক ভাষা প্রক্রিয়াকরণ আকর্ষণীয়।",
             "प्राकृतिक भाषा प्रसंस्करण रोचक है।",
             "Le traitement automatique du langage naturel est intéressant."
            ]



# ===== NLP_lab06 =====

!pip install nltk spacy scikit-learn
!python -m spacy download en_core_web_sm

import nltk
nltk.download('punkt')
nltk.download('punkt_tab')
nltk.download('averaged_perceptron_tagger')
nltk.download('averaged_perceptron_tagger_eng')

#Part-of-Speech (PoS) tags
text = "Natural Language Processing enables computers to understand human language."
tokens = nltk.word_tokenize(text)
print("Tokens:", tokens)
pos_tags = nltk.pos_tag(tokens)
print("PoS Tags:", pos_tags)

#Analyze PoS Distribution
from collections import Counter

tag_counts = Counter(tag for word, tag in pos_tags)
print("PoS Tag Distribution:", tag_counts)

#Use PoS Tags as Features

def pos_features(sentence):

   tokens = nltk.word_tokenize(sentence)

   tags = [tag for word, tag in nltk.pos_tag(tokens)]

   return Counter(tags)

sentence1 = "The movie was fantastic and thrilling"

sentence2 = "The movie was boring and slow"

print("Sentence 1 Features:", pos_features(sentence1))

print("Sentence 2 Features:", pos_features(sentence2))

#Simple Text Classification Using PoS Features
from sklearn.feature_extraction import DictVectorizer
from sklearn.linear_model import LogisticRegression

sentences = [
    "The product is excellent",
    "I hated the service",
    "Amazing experience",
    "Very disappointing"
]

labels = [1, 0, 1, 0]
features = [pos_features(s) for s in sentences]
vec = DictVectorizer()
X = vec.fit_transform(features)
model = LogisticRegression()
model.fit(X, labels)
test_sentence = ["The experience was wonderful"]
test_features = vec.transform([pos_features(test_sentence[0])])
prediction = model.predict(test_features)
print("Prediction:", "Positive" if prediction[0] == 1 else "Negative")



# ===== NLP_lab07 =====

!pip install nltk scikit-learn pandas numpy matplotlib

#Treebank dataset
import nltk
nltk.download('treebank')
from nltk.corpus import treebank

sentences = treebank.tagged_sents()
train_data = sentences[:3000]
test_data = sentences[3000:]

#Rule-Based PoS Tagger
from nltk.tag import RegexpTagger, DefaultTagger
patterns = [
    (r'.*ing$', 'VBG'),
    (r'.*ed$', 'VBD'),
    (r'.*s$', 'NNS'),
    (r'.*', 'NN')   # default
]
rule_tagger = RegexpTagger(patterns)
print(rule_tagger.tag(["playing", "dogs", "walked"]))
default_tagger = DefaultTagger('NN')  # assign noun to all
accuracy = default_tagger.accuracy(test_data)

print("Rule-Based Accuracy:", accuracy)

#Statistical PoS Tagger (HMM)

from nltk.tag import hmm
trainer = hmm.HiddenMarkovModelTrainer()
hmm_tagger = trainer.train(train_data)

# Test sentence
print(hmm_tagger.tag(["The", "dog", "runs"]))

# Accuracy
accuracy = hmm_tagger.accuracy(test_data)

print("HMM Accuracy:", accuracy)

#Machine Learning PoS Tagger
from sklearn.feature_extraction import DictVectorizer
from sklearn.linear_model import LogisticRegression

def word_features(word):
   return {
       'word': word,
       'is_upper': word.isupper(),
       'is_digit': word.isdigit(),
       'suffix': word[-2:]
   }

X = []
y = []

for sent in train_data:
   for word, tag in sent:

       X.append(word_features(word))
       y.append(tag)

vec = DictVectorizer()
X_vec = vec.fit_transform(X)
model = LogisticRegression(max_iter=200)
model.fit(X_vec, y)

# Test on one sentence

test_words = ["The", "cat", "sat"]
test_features = [word_features(w) for w in test_words]
test_vec = vec.transform(test_features)

print(list(zip(test_words, model.predict(test_vec))))



# ===== NLP_lab08 =====

from collections import defaultdict
# States
states = ["LOWER", "TITLE"]
# Training data
training_data = [
    ("this", "LOWER"), ("is", "LOWER"), ("nasa", "LOWER"),
    ("research", "LOWER"),
    ("This", "TITLE"), ("Is", "TITLE"), ("NASA", "TITLE"),
    ("Research", "TITLE")
]

# Words that should remain fully uppercase
special_upper = {"nasa"}
# Count emissions
emission_counts = defaultdict(lambda: defaultdict(int))
state_counts = defaultdict(int)
for word, state in training_data:
    state_counts[state] += 1
    emission_counts[state][word.lower()] += 1
# Convert to probabilities
emission_prob = {
    state: {
        word: count / state_counts[state]
        for word, count in emission_counts[state].items()
    }
    for state in states
}
# Simple transition probabilities
transition_prob = {
    "LOWER": {"LOWER": 0.5, "TITLE": 0.5},
    "TITLE": {"LOWER": 0.5, "TITLE": 0.5}
}

# Viterbi Algorithm
def viterbi(words):
    V = [{}]
    path = {}
    # Initialization
    for s in states:
        V[0][s] = emission_prob[s].get(words[0], 1e-6)
        path[s] = [s]
    # Recursion
    for i in range(1, len(words)):
        V.append({})
        new_path = {}
        for curr in states:
            prob, prev = max(
                (V[i-1][p] * transition_prob[p][curr] *
                 emission_prob[curr].get(words[i], 1e-6), p)
                for p in states
            )
            V[i][curr] = prob
            new_path[curr] = path[prev] + [curr]
        path = new_path
    return path[max(V[-1], key=V[-1].get)]

# Convert function
def convert(text):
    words = text.split()
    tags = viterbi([w.lower() for w in words])
    result = []
    for i, (word, tag) in enumerate(zip(words, tags)):
        # Rule 1: First word always TITLE
        if i == 0:
            result.append(word.capitalize())
        # Rule 2: Special uppercase words
        elif word.lower() in special_upper:
            result.append(word.upper())
        # Rule 3: HMM decision
        elif tag == "TITLE":
            result.append(word.capitalize())
        else:
            result.append(word.lower())
    return " ".join(result)

# Test
text = "this is nasa research"
print("Input :", text)
print("Output:", convert(text))



# ===== NLP_lab09 =====

!pip install transformers datasets sacrebleu sentencepiece

from transformers import MarianMTModel, MarianTokenizer
import sacrebleu

model_name = "Helsinki-NLP/opus-mt-en-hi"
tokenizer = MarianTokenizer.from_pretrained(model_name)
model = MarianMTModel.from_pretrained(model_name, use_safetensors=True)

def translate(text):
   inputs = tokenizer(text, return_tensors="pt", padding=True)
   outputs = model.generate(**inputs)
   return tokenizer.decode(outputs[0], skip_special_tokens=True)

input_text = "natural language processing is simple"
translated_text = translate(input_text)
print("Input:", input_text)
print("Translated:", translated_text)

reference = ["प्राकृतिक भाषा प्रक्रिया सरल है"]
candidate = [translated_text]
bleu = sacrebleu.corpus_bleu(candidate, [reference])
print("BLEU Score:", bleu.score)



# ===== NLP_lab10 =====

!pip install transformers torch sentence-transformers
!python -m pip install --upgrade pip

from transformers import pipeline

fill_mask = pipeline("fill-mask", model="distilbert-base-uncased")
sentence = "She is a very [MASK] student."
results = fill_mask(sentence)
print("Top Candidates:")

for r in results:
   print(r['token_str'], "->", round(r['score'], 3))

from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer('all-MiniLM-L6-v2')
original = "She is a very talented student."
substitute = "She is a very intelligent student."
emb1 = model.encode(original, convert_to_tensor=True)
emb2 = model.encode(substitute, convert_to_tensor=True)
similarity = util.cos_sim(emb1, emb2)

print("Similarity:", similarity.item())


    """
    print(code_to_print)


