import pandas as pd
import time
import numpy as np
import matplotlib.pyplot as plt
import os
import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader
from collections import Counter
import torch.nn.functional
import pennylane as qml
from pennylane import numpy as np
from datasets import load_dataset
from tqdm import tqdm  # 添加进度条
import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
import re
from sklearn.model_selection import train_test_split
from qiskit import QuantumCircuit
from qiskit.visualization import circuit_drawer
from sklearn.metrics import f1_score, recall_score, accuracy_score
import chardet
import random
# 设置随机种子
seed = 42
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
# 下载 nltk punkt 资源
nltk.download('punkt')
nltk.download('stopwords')

min_freq = 1
epoch = 40
sequence_length = 16
batch_size = 32
hidden_size = 64
embedding_dim = 100  # 词嵌入维度 与mat1有关
dropout_rate = 0.5
lr = 0.005
# 定义数据集路径
dataset_path = r'C:\Users\Administrator\Desktop\MR\MR dataset.csv'

# 读取CSV数据集，假设每个记录的标签和文本之间使用逗号分隔
data_df = pd.read_csv(dataset_path, header=None, sep=',', names=['label', 'text'], quotechar='"', engine='python')

# 打印整个数据集
print(data_df.head(100))

# 文本清洗函数
def clean_text(text):
    # 转换为小写
    text = str(text).lower()
    # 移除 HTML 标签
    text = re.sub(r'<.*?>', '', text)
    # 移除 URL
    text = re.sub(r'http\S+|www\S+|https\S+', '', text, flags=re.MULTILINE)
    # 移除标点符号和数字
    text = re.sub(r'\d+', '', text)
    text = re.sub(r'[^\w\s]', '', text)
    # 分词
    words = word_tokenize(text)
    # 移除停用词
    stop_words = set(stopwords.words('english'))- {'not', 'no', 'nor', 'but'}
    words = [word for word in words if word not in stop_words]
    return ' '.join(words)

# 清洗数据集文本
data_df['text'] = data_df['text'].apply(clean_text)

# 随机打乱数据
data_df = data_df.sample(frac=1, random_state=seed).reset_index(drop=True)

# 抽样训练集、验证集、测试集
train_sample = data_df[:8530]
remaining_data = data_df[8530:]
val_sample, test_sample = train_test_split(remaining_data, test_size=1067, random_state=42, stratify=remaining_data['label'])

# 定义一个函数来标记和编码句子
def tokenize_and_encode(sentences, vocab, seq_length):
    token_ids = [
        [vocab.get(token, vocab['<UNK>']) for token in sentence.split()]
        for sentence in sentences
    ]
    # 填充序列
    token_ids = [
        t_ids[:seq_length] if len(t_ids) > seq_length else t_ids + [vocab['<PAD>']] * (seq_length - len(t_ids))
        for t_ids in token_ids
    ]
    return token_ids

# 从训练数据集创建词汇表
all_train_tokens = [token for sentence in train_sample['text'] for token in sentence.split()]

# 计算所有训练数据中单词的频率
word_counts = Counter(all_train_tokens)

# 打印词汇表中每个单词的频率，按照频率从高到低排序
#for word, count in word_counts.most_common():
    #print(f"{word}: {count}")

# 只保留出现次数大于等于阈值的单词

filtered_words = [word for word, count in word_counts.items() if count >= min_freq]
vocabulary = {word: i + 2 for i, word in enumerate(filtered_words)}
vocabulary['<UNK>'] = 1
vocabulary['<PAD>'] = 0

# 打印词汇表大小
print(f"Vocabulary size: {len(vocabulary)}")

# 对数据集进行标记和编码

train_token_ids = tokenize_and_encode(train_sample['text'], vocabulary, sequence_length)
val_token_ids = tokenize_and_encode(val_sample['text'], vocabulary, sequence_length)
# test_token_ids = tokenize_and_encode(test_sample['text'], vocabulary, sequence_length)

# 打印标记和编码后的数据样本
print("\n标记和编码后的数据样本:")
for sample in train_token_ids[:10]:
     print(" ".join(map(str, sample)))

# 创建自定义 PyTorch 数据集
class SentimentDataset(Dataset):
    def __init__(self, token_ids, labels):
        self.token_ids = token_ids
        self.labels = labels

    def __len__(self):
        return len(self.token_ids)

    def __getitem__(self, idx):
        return torch.tensor(self.token_ids[idx], dtype=torch.long), torch.tensor(self.labels[idx], dtype=torch.long)

# 创建自定义 Dataset 实例

train_dataset = SentimentDataset(train_token_ids, train_sample['label'].tolist())
val_dataset = SentimentDataset(val_token_ids, val_sample['label'].tolist())
# test_dataset = SentimentDataset(test_token_ids, test_df['label'].tolist())

# 为每个数据集创建 DataLoader

train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)
# test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

data_df['length'] = data_df['text'].apply(lambda x: len(x.split()))

# 打印数据集大小和正负样本数量
print(f"Train set size: {len(train_sample)}")
print(f"Validation set size: {len(val_sample)}")
print(f"Test set size: {len(test_sample)}")

print(f"Train Positive count: {len(train_sample[train_sample['label'] == 1])}")
print(f"Train Negative count: {len(train_sample[train_sample['label'] == 0])}")

print(f"Validation Positive count: {len(val_sample[val_sample['label'] == 1])}")
print(f"Validation Negative count: {len(val_sample[val_sample['label'] == 0])}")

print(f"Test Positive count: {len(test_sample[test_sample['label'] == 1])}")
print(f"Test Negative count: {len(test_sample[test_sample['label'] == 0])}")

plt.figure(figsize=(10, 6))
plt.hist(data_df['length'], bins=50, alpha=0.75, color='blue')
plt.title('Sentence Length Distribution')
plt.xlabel('Sentence Length')
plt.ylabel('Frequency')
plt.grid(True)
plt.show()
#自注意力类

# 量子LSTM
class QLSTM(nn.Module):
    def __init__(self,
                 input_size,
                 hidden_size,
                 n_qubits=4,
                 n_qlayers=1,
                 n_vrotations=3,
                 batch_first=True,
                 return_sequences=False,
                 return_state=False,
                 backend="default.qubit",
                 dropout_rate=dropout_rate):
        super(QLSTM, self).__init__()
        self.n_inputs = input_size
        self.hidden_size = hidden_size
        self.concat_size = self.n_inputs + self.hidden_size
        self.n_qubits = n_qubits
        self.n_qlayers = n_qlayers
        self.n_vrotations = n_vrotations
        self.backend = backend

        self.batch_first = batch_first
        self.return_sequences = return_sequences
        self.return_state = return_state

        self.wires_forget = [f"wire_forget_{i}" for i in range(self.n_qubits)]
        self.wires_input = [f"wire_input_{i}" for i in range(self.n_qubits)]
        self.wires_update = [f"wire_update_{i}" for i in range(self.n_qubits)]
        self.wires_output = [f"wire_output_{i}" for i in range(self.n_qubits)]
        self.wires_output_h = [f"wire_output_h_{i}" for i in range(self.n_qubits)]
        # 定义线性层
        self.fc = nn.Linear(self.hidden_size, 2)
        # 初始化权重形状
        self.weight_shapes = {"weights": (self.n_qlayers, self.n_vrotations, self.n_qubits)}
        # 定义量子设备
        # self.dev = qml.device('default.qubit', wires=4)
        self.dev_forget = qml.device(self.backend, wires=self.wires_forget)
        self.dev_input = qml.device(self.backend, wires=self.wires_input)
        self.dev_update = qml.device(self.backend, wires=self.wires_update)
        self.dev_output = qml.device(self.backend, wires=self.wires_output)
        self.dev_output_h = qml.device(self.backend, wires=self.wires_output_h)
        # 定义 ansatz 方法
        self.ansatz = self.ansatz
        # 初始化dropout层
        self.dropout = nn.Dropout(dropout_rate)

        # QNode实例化
        self.qlayer_forget = qml.QNode(self._circuit_forget, self.dev_forget, interface="torch")
        self.qlayer_input = qml.QNode(self._circuit_input, self.dev_input, interface="torch")
        self.qlayer_update = qml.QNode(self._circuit_update, self.dev_update, interface="torch")
        self.qlayer_output = qml.QNode(self._circuit_output, self.dev_output, interface="torch")
        self.qlayer_output_h = qml.QNode(self._circuit_output_h, self.dev_output_h, interface="torch")
        # 定义权重形状
        weight_shapes = {"weights": (self.n_qlayers, self.n_vrotations, self.n_qubits)}

        # print(f"weight_shapes = (n_qlayers, n_vrotations, n_qubits) = ({self.n_qlayers}, {self.n_vrotations}, {self.n_qubits})")

        # 定义线性层
        self.clayer_in = nn.Linear(self.concat_size, self.n_qubits)
        self.clayer_out = nn.Linear(self.n_qubits, self.hidden_size)
        self.clayer_out_f = nn.Linear(self.n_qubits, self.hidden_size)
        self.clayer_out_i = nn.Linear(self.n_qubits, self.hidden_size)
        self.clayer_out_g = nn.Linear(self.n_qubits, self.hidden_size)
        self.clayer_out_o = nn.Linear(self.n_qubits, self.hidden_size)
        self.TorchLayers = {
            'forget': qml.qnn.TorchLayer(self.qlayer_forget, self.weight_shapes),
            'input': qml.qnn.TorchLayer(self.qlayer_input, self.weight_shapes),
            'update': qml.qnn.TorchLayer(self.qlayer_update, self.weight_shapes),
            'output': qml.qnn.TorchLayer(self.qlayer_output, self.weight_shapes),
            'output_h': qml.qnn.TorchLayer(self.qlayer_output_h, self.weight_shapes)
        }

    def ansatz(self, params, wires_type):

        # Entangling layer.
        for i in range(1, 3):
            for j in range(self.n_qubits):
                if j + i < self.n_qubits:
                    qml.CNOT(wires=[wires_type[j], wires_type[j + i]])
                else:
                    qml.CNOT(wires=[wires_type[j], wires_type[j + i - self.n_qubits]])

            # Variational layer.
            for i in range(self.n_qubits):
                qml.RX(params[0][i], wires=wires_type[i])
                qml.RY(params[1][i], wires=wires_type[i])
                qml.RZ(params[2][i], wires=wires_type[i])

    def vqc(self, features, weights, wires_type):
        # 将features转换为PyTorch张量（如果它是一个NumPy数组）
        if isinstance(features, np.ndarray):
            features = torch.tensor(features, dtype=torch.float32)
        # 检查features是否为一维张量，如果是，则添加一个新的批次维度
        if features.dim() == 1:
            features = features.unsqueeze(0)  # 将其变为二维张量，形状为[1, 特征数]
        if not isinstance(features, torch.Tensor):
            features = torch.tensor(features, dtype=torch.float32)
        # print("features形状:", features.shape)
        # print("Weights shape:", weights.shape)

        # 确保 features 的长度等于量子位的数量
        features = features[:, :self.n_qubits]
        ry_params = torch.atan2(features, torch.ones_like(features))
        rz_params = torch.atan2(features ** 2, torch.ones_like(features))

        # ry_params = [torch.arctan(feature) for feature in features]
        # rz_params = [torch.arctan(feature ** 2) for feature in features]
        # 应用Hadamard门到每个量子位上
        for i in range(self.n_qubits):
            qml.Hadamard(wires=wires_type[i])

        # 对量子位进行RY和RZ旋转
        for i in range(self.n_qubits):
            qml.RY(ry_params[:, i], wires=wires_type[i])
            qml.RZ(rz_params[:, i], wires=wires_type[i])

        # 创建量子位之间的纠缠
        for i in range(1, 3):
            for j in range(self.n_qubits):
                if j + i < self.n_qubits:
                    qml.CNOT(wires=[wires_type[j], wires_type[j + i]])
                else:
                    qml.CNOT(wires=[wires_type[j], wires_type[j + i - self.n_qubits]])

        # 再次对量子位进行RX、RY、RZ旋转
        for layer in range(self.n_qlayers):
            for i in range(self.n_qubits):
                qml.RX(weights[layer, 0, i], wires=wires_type[i])
                qml.RY(weights[layer, 1, i], wires=wires_type[i])
                qml.RZ(weights[layer, 2, i], wires=wires_type[i])
    # 定义量子电路，模拟LSTM中的门控机制
    def _circuit_forget(self, inputs, weights):
        self.vqc(inputs, weights, self.wires_forget)
        return [qml.expval(qml.PauliZ(wires=i)) for i in self.wires_forget]

    def _circuit_input(self, inputs, weights):
        self.vqc(inputs, weights, self.wires_input)
        return [qml.expval(qml.PauliZ(wires=i)) for i in self.wires_input]

    def _circuit_update(self, inputs, weights):
        self.vqc(inputs, weights, self.wires_update)
        return [qml.expval(qml.PauliZ(wires=i)) for i in self.wires_update]

    def _circuit_output(self, inputs, weights):
        self.vqc(inputs, weights, self.wires_output)
        return [qml.expval(qml.PauliZ(wires=i)) for i in self.wires_output]
    # 计算隐藏状态输出

    def _circuit_output_h(self, inputs, weights):
        self.vqc(inputs, weights, self.wires_output_h)
        return [qml.expval(qml.PauliZ(wires=i)) for i in self.wires_output_h]

    def forward(self, x, init_states=None):

        batch_size, seq_length, _ = x.size()

        if init_states is None:
            h_t = torch.zeros(batch_size, self.hidden_size, dtype=torch.float32)  # hidden state (output)
            c_t = torch.zeros(batch_size, self.hidden_size, dtype=torch.float32)  # cell state
        else:
            h_t, c_t = init_states

            # 批处理整个序列
        for t in range(seq_length):
            x_t = x[:, t, :]  # 获取当前时间步的特征
            # 选择 h_t 的最后一层，以匹配 x_t 的维度
            h_t_last_layer = h_t[-1, :, :]  # 假设 h_t 的形状是 [num_layers, batch_size, hidden_size]

            # 现在 h_t_last_layer 的形状是 [batch_size, hidden_size]，可以和 x_t 连接
            combined = torch.cat((h_t_last_layer, x_t), dim=1)

            # 将拼接后的数据通过一个线性层以匹配量子位的维度
            combined = self.clayer_in(combined)

            # 用量子层处理整个批次的数据
            f_t_intermediate = self.TorchLayers['forget'](combined)
            i_t_intermediate = self.TorchLayers['input'](combined)
            g_t_intermediate = self.TorchLayers['update'](combined)
            o_t_intermediate = self.TorchLayers['output'](combined)

            # 将中间结果通过另一个线性层转换回隐藏状态的维度
            f_t = torch.sigmoid(self.clayer_out_f(f_t_intermediate))
            i_t = torch.sigmoid(self.clayer_out_i(i_t_intermediate))
            g_t = torch.tanh(self.clayer_out_g(g_t_intermediate))
            o_t = torch.sigmoid(self.clayer_out_o(o_t_intermediate))

            # 根据 LSTM 的更新规则更新隐藏状态和细胞状态
            c_t = f_t * c_t + i_t * g_t
            h_t = o_t * torch.tanh(c_t)
            # print("h_t shape:", h_t.shape)
            # 使用最后的隐藏状态通过一个线性层以得到最终输出
            h_t = self.dropout(h_t)  # 将dropout应用到隐藏状态

        output = self.fc(h_t)
        # print(f"Output shape: {output.shape}")
        return output

# 电路可视化实现
qlstm_instance = QLSTM(input_size=10, hidden_size=64, n_qubits=4, n_qlayers=1, n_vrotations=3, backend="default.qubit")
qlstm_instance.visualize_qiskit_circuit()
class QShallowClassificationLSTM(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_size, n_qubits=4, n_qlayers=1, num_classes=2, dropout_rate=dropout_rate):
        super().__init__()
        self.vocab_size = vocab_size
        self.hidden_size = hidden_size
        self.num_layers = 1
        self.num_classes = num_classes
        self.embedding = nn.Embedding(vocab_size, embedding_dim)

        # 添加自注意力层

        # 添加 Dropout 层
        self.dropout = nn.Dropout(dropout_rate)
        self.lstm = QLSTM(
            # input_size=num_sensors,
            # 使用embedding_dim 作为 LSTM 的输入维度
            input_size=embedding_dim,
            hidden_size=hidden_size,
            batch_first=True,
            n_qubits=n_qubits,
            n_qlayers=n_qlayers,

        )
        self.linear = nn.Linear(in_features=2, out_features=2)


    def forward(self, x):
        batch_size = x.shape[0]

        x = self.embedding(x)
        # print(f"Embedding layer output shape: {x.shape}")
        # 应用自注意力层


        # 应用 Dropout
        x = self.dropout(x)

        h0 = torch.zeros(self.num_layers, batch_size, self.hidden_size)
        c0 = torch.zeros(self.num_layers, batch_size, self.hidden_size)

        # QLSTM 返回所有时间步的输出
        outputs = self.lstm(x, (h0, c0))

        # 选择最后一个时间步的隐藏状态，它的形状为 [batch_size, hidden_units]
        last_hidden_state = outputs.squeeze(0)

        # 将最后一个隐藏状态传递给线性层
        out = self.linear(last_hidden_state)

        return out



# 词汇表大小
vocab_size = len(vocabulary)


# 实例化分类模型和量子分类模型

Qmodel = QShallowClassificationLSTM(vocab_size=len(vocabulary),
                                    embedding_dim=embedding_dim,
                                    hidden_size=hidden_size,
                                    n_qubits=4,
                                    n_qlayers=1,
                                    num_classes=2)

# 定义损失函数和优化器
loss_function = nn.CrossEntropyLoss()  # 更新为分类任务的损失函数

Qoptimizer = torch.optim.Adam(Qmodel.parameters(), lr=lr)

#  计算模型分类准确率
def calculate_accuracy_and_scores(data_loader, Qmodel):
    all_labels = []
    all_predictions = []

    Qmodel.eval()
    with torch.no_grad():
        for X, y in data_loader:
            outputs = Qmodel(X)
            _, predicted = torch.max(outputs.data, 1)
            all_labels.extend(y.cpu().numpy())
            all_predictions.extend(predicted.cpu().numpy())

    # Compute metrics using all labels and predictions
    accuracy = 100 * accuracy_score(all_labels, all_predictions)
    recall = recall_score(all_labels, all_predictions, pos_label=1)
    f1 = f1_score(all_labels, all_predictions, pos_label=1)

    return accuracy, recall, f1

#  训练函数
def train_model(data_loader, Qmodel, loss_function, optimizer,  progress_bar):
    total_loss = 0
    Qmodel.train()

    for X, y in data_loader:
        output = Qmodel(X)
        loss = loss_function(output, y)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_loss += loss.item()
        progress_bar.update(1)  # 更新进度条

    avg_loss = total_loss / len(data_loader)
    return avg_loss

#  测试函数
def test_model(data_loader, Qmodel, loss_function, progress_bar):

    total_loss = 0
    Qmodel.eval()
    with torch.no_grad():
        for X, y in data_loader:
            output = Qmodel(X)
            total_loss += loss_function(output, y).item()
            progress_bar.update(1)  # 更新进度条

    avg_loss = total_loss / len(data_loader)
    return avg_loss

# 初始化训练、测试、验证损失，验证集准确率
quantum_loss_train = []
quantum_loss_test = []
quantum_loss_val = []
quantum_accuracy_val = []
print("Quantum Untrained test\n--------")

# 初始化存储准确率的列表
train_accuracy_list = []
train_recall_list = []
train_f1_list = []

val_accuracy_list = []
val_recall_list = []
val_f1_list = []

# test_accuracy_list = []
# test_recall_list = []
# test_f1_list = []

for ix_epoch in range(epoch):
    #  print(f"Epoch {ix_epoch+1}\n---------")
    total_steps = len(train_loader) + len(val_loader)
    progress_bar = tqdm(total=total_steps, desc=f"Epoch {ix_epoch + 1}")

    # 训练损失
    train_loss = train_model(train_loader, Qmodel, loss_function, Qoptimizer, progress_bar)

    # 测试损失
    # test_loss = test_model(test_loader, Qmodel, loss_function, progress_bar)

    # 验证损失
    val_loss = test_model(val_loader, Qmodel, loss_function, progress_bar)

    # 计算量子模型的准确率、召回率和F1分数
    quantum_train_accuracy, train_recall, train_f1 = calculate_accuracy_and_scores(train_loader, Qmodel)
    # quantum_test_accuracy, test_recall, test_f1 = calculate_accuracy_and_scores(test_loader, Qmodel)

    # 计算验证集准确率、召回率和F1分数
    val_accuracy, val_recall, val_f1 = calculate_accuracy_and_scores(val_loader, Qmodel)

    # 关闭进度条
    progress_bar.close()

    train_accuracy, train_recall, train_f1 = calculate_accuracy_and_scores(train_loader, Qmodel)
    val_accuracy, val_recall, val_f1 = calculate_accuracy_and_scores(val_loader, Qmodel)
    # test_accuracy = calculate_accuracy(test_loader, Qmodel)

    # 将准确率添加到相应的列表中
    train_accuracy_list.append(train_accuracy)
    train_recall_list.append(train_recall)
    train_f1_list.append(train_f1)

    val_accuracy_list.append(val_accuracy)
    val_recall_list.append(val_recall)
    val_f1_list.append(val_f1)

    # test_accuracy_list.append(test_accuracy)
    # test_recall_list.append(test_recall)
    # test_f1_list.append(test_f1)

    # 打印损失和评估指标信息
    print(f"\nTrain Loss: {train_loss:.4f}")
    # print(f"Test Loss: {test_loss:.4f}")
    print(f"Validation Loss: {val_loss:.4f}")

    print(f"Train Accuracy: {quantum_train_accuracy:.2f}%")
    # print(f"Test Accuracy: {quantum_test_accuracy:.2f}%")
    print(f"Validation Accuracy: {val_accuracy:.2f}%\n")

    print(f"Train Recall: {train_recall:.2f}")
    # print(f"Test Recall: {test_recall:.2f}")
    print(f"Train F1 Score: {train_f1:.2f}")

    print(f"Validation Recall: {val_recall:.2f}")
    # print(f"Test F1 Score: {test_f1:.2f}")
    print(f"Validation F1 Score: {val_f1:.2f}\n")

    # 记录训练、测试、验证损失
    quantum_loss_train.append(train_loss)
    # quantum_loss_test.append(test_loss)
    quantum_loss_val.append(val_loss)
    quantum_accuracy_val.append(val_accuracy)
train_eval_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=False)

# 绘制量子 QLSTM 模型的损失图表

plt.figure(figsize=(12, 6))
plt.plot(quantum_loss_train, label='Quantum Train Loss')
plt.plot(quantum_loss_test, label='Quantum Test Loss')
plt.plot(quantum_loss_val, label='Validation Loss')
plt.title('Quantum LSTM Model Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Validation Loss across Folds')
plt.xlabel('Epoch')
plt.ylabel('Validation Loss')
plt.legend()
plt.show()

# 绘制准确率图表
plt.figure(figsize=(12, 6))
plt.plot(train_accuracy_list, label='Train Accuracy')
plt.plot(val_accuracy_list, label='Validation Accuracy')
# plt.plot(test_accuracy_list, label='Test Accuracy')

plt.plot(train_recall_list, label='Train Recall')
plt.plot(val_recall_list, label='Validation Recall')
# plt.plot(test_recall_list, label='Test Recall')

plt.plot(train_f1_list, label='Train F1 Score')
plt.plot(val_f1_list, label='Validation F1 Score')
# plt.plot(test_f1_list, label='Test F1 Score')

# 模型性能指标
plt.title('Model Evaluation Metrics Across Epochs')
plt.xlabel('Epoch')
plt.ylabel('Metrics')
plt.legend()
plt.show()
请你首先检查我的模型中是否涉及到了数据从经典态转换到量子态，并且从量子态转换到经典态，如果没有请你修改我的代码能够实现数据形态的转换，，并且能够构成完整的量子神经网络