import sys
import os
import pennylane as qml
from pennylane import numpy as np
import tensorflow as tf
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.decomposition import PCA
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from tensorflow.keras.layers import Dense

# 导入修改后的采样器和训练模块
from importance_sampling.training import ConstantTimeImportanceTraining
from importance_sampling.samplers import ConstantTimeSampler
from importance_sampling.datasets import InMemoryDataset
from importance_sampling.reweighting import BiasedReweightingPolicy

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 使用黑体
plt.rcParams['axes.unicode_minus'] = False    # 解决负号显示问题

# 重定向 stdout 以捕获打印输出
class Tee(object):
    def __init__(self, *files):
        self.files = files
    def write(self, obj):
        for f in self.files:
            f.write(obj)
            f.flush()
    def flush(self):
        for f in self.files:
            f.flush()

output_file = open('QNNImprove_Iris.txt', 'w', encoding='utf-8')  # 修改输出文件名称
original_stdout = sys.stdout
sys.stdout = Tee(sys.stdout, output_file)

def save_plot(fig, filename):
    fig.savefig(filename)
    plt.close(fig)

def plot_history(history):
    # 绘制训练和验证准确率
    fig, ax = plt.subplots(figsize=(10, 5))
    ax.plot(history.history['accuracy'])
    ax.plot(history.history['val_accuracy'])
    ax.set_title('模型准确率')
    ax.set_ylabel('准确率')
    ax.set_xlabel('轮次')
    ax.legend(['训练', '验证'], loc='upper left')
    save_plot(fig, 'accuracy_plot_iris.png')  # 修改图表名称

    # 绘制训练和验证损失
    fig, ax = plt.subplots(figsize=(10, 5))
    ax.plot(history.history['loss'])
    ax.plot(history.history['val_loss'])
    ax.set_title('模型损失')
    ax.set_ylabel('损失')
    ax.set_xlabel('轮次')
    ax.legend(['训练', '验证'], loc='upper left')
    save_plot(fig, 'loss_plot_iris.png')  # 修改图表名称

def calculate_accuracy_fluctuation(history):
    val_accuracies = history.history['val_accuracy']
    mean_accuracy = np.mean(val_accuracies)
    fluctuation = np.sum(np.abs(val_accuracies - mean_accuracy))
    return mean_accuracy, fluctuation

# 定义设备，使用 4 个量子比特
n_qubits = 4  # 根据 Iris 数据集的特征数量调整量子比特数
dev = qml.device("default.qubit", wires=n_qubits)

def encode_features(inputs, n_qubits):
    """
    使用角度编码将输入特征编码到量子电路中。
    将特征映射到量子比特的旋转角度。
    """
    # 将特征映射到量子比特
    for i in range(n_qubits):
        qml.RY(inputs[i], wires=i)

@qml.qnode(dev, interface='tf', diff_method='backprop')
def quantum_neural_network(inputs, rot_weights):
    # 编码输入特征到量子比特
    encode_features(inputs, n_qubits)

    # 应用变分层
    for layer in range(rot_weights.shape[0]):
        for qubit in range(n_qubits):
            rot_angles = rot_weights[layer, qubit, :]  # 形状：(3,)
            qml.Rot(rot_angles[0], rot_angles[1], rot_angles[2], wires=qubit)
        # 修改纠缠方式，使用全连接纠缠
        for i in range(n_qubits):
            for j in range(i + 1, n_qubits):
                qml.CRX(np.pi / 2, wires=[i, j])

    # 测量期望值
    return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]

# 定义用于绘制电路的 QNode
@qml.qnode(dev)
def quantum_neural_network_draw(inputs, rot_weights):
    # 编码输入特征到量子比特
    encode_features(inputs, n_qubits)

    # 应用变分层
    for layer in range(rot_weights.shape[0]):
        for qubit in range(n_qubits):
            rot_angles = rot_weights[layer, qubit, :]  # 形状：(3,)
            qml.Rot(rot_angles[0], rot_angles[1], rot_angles[2], wires=qubit)
        # 修改纠缠方式，使用全连接纠缠
        for i in range(n_qubits):
            for j in range(i + 1, n_qubits):
                qml.CRX(np.pi / 2, wires=[i, j])

    # 测量期望值
    return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]

# 定义量子神经网络层
class QuantumNeuralNetworkLayer(tf.keras.layers.Layer):
    def __init__(self, n_qubits, n_layers, **kwargs):
        super(QuantumNeuralNetworkLayer, self).__init__(**kwargs)
        self.n_qubits = n_qubits
        self.n_layers = n_layers

        # 定义旋转权重
        init_weights = tf.random.uniform(
            shape=(n_layers, n_qubits, 3),
            minval=0,
            maxval=2 * np.pi,
            dtype=tf.float32
        )
        self.rot_weights = tf.Variable(
            initial_value=init_weights,
            trainable=True,
            name='rot_weights'
        )

    def call(self, inputs):
        def apply_qnode(x):
            x = tf.cast(x, dtype=tf.float32)
            result = quantum_neural_network(x, self.rot_weights)
            return tf.cast(result, dtype=tf.float32)

        outputs = tf.map_fn(
            apply_qnode,
            inputs,
            dtype=tf.float32
        )

        return outputs

# 定义量子神经网络模型
class QuantumNeuralNetwork(tf.keras.Model):
    def __init__(self, n_qubits, n_layers, num_classes, **kwargs):
        super(QuantumNeuralNetwork, self).__init__(**kwargs)
        self.n_qubits = n_qubits
        self.n_layers = n_layers
        self.num_classes = num_classes

        # 量子层
        self.quantum_layer = QuantumNeuralNetworkLayer(n_qubits, n_layers)

        # 经典输出层
        self.output_layer = tf.keras.layers.Dense(num_classes, activation='softmax')

    def call(self, inputs):
        q_output = self.quantum_layer(inputs)
        return self.output_layer(q_output)

if __name__ == "__main__":
    # **重要性采样始终启用**

    # 加载 Iris 数据集
    iris = load_iris()
    X = iris['data'].astype(np.float32)
    y = iris['target'].reshape(-1, 1)

    # One-hot 编码目标标签
    encoder = OneHotEncoder(sparse_output=False)
    y_encoded = encoder.fit_transform(y).astype(np.float32)
    num_classes = y_encoded.shape[1]

    # 先划分数据集
    x_train, x_test, y_train, y_test = train_test_split(
        X, y_encoded, test_size=0.3, random_state=42, stratify=y_encoded
    )

    # 特征标准化 (仅在训练集上拟合)
    scaler = StandardScaler()
    x_train_scaled = scaler.fit_transform(x_train)
    x_test_scaled = scaler.transform(x_test)

    # 使用 PCA 进行降维 (仅在训练集上拟合)
    n_features = n_qubits  # 将特征数量降至与量子比特数量一致
    pca = PCA(n_components=n_features)
    x_train_reduced = pca.fit_transform(x_train_scaled)
    x_test_reduced = pca.transform(x_test_scaled)

    # 将特征归一化到 [0, π] (基于训练集的最小值和最大值)
    x_min = x_train_reduced.min(axis=0)
    x_max = x_train_reduced.max(axis=0)
    x_train_normalized = (x_train_reduced - x_min) / (x_max - x_min) * np.pi
    x_test_normalized = (x_test_reduced - x_min) / (x_max - x_min) * np.pi

    x_train = x_train_normalized
    x_test = x_test_normalized

    learning_rate = 0.1  # 学习率
    epochs = 200  # 训练轮次
    batch_size = 16
    n_layers = 3  # 增加量子层数

    model = QuantumNeuralNetwork(
        n_qubits=n_qubits,
        n_layers=n_layers,
        num_classes=num_classes
    )

    # 在主程序中编译模型
    # 使用 reduction='none' 以便我们可以为每个样本分配权重
    loss_fn = tf.keras.losses.CategoricalCrossentropy(from_logits=False, reduction=tf.keras.losses.Reduction.NONE)

    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
        loss=loss_fn,
        metrics=['accuracy']
    )

    def is_quantum_model(model):
        for layer in model.layers:
            if isinstance(layer, QuantumNeuralNetworkLayer):
                return True
        return False

    if is_quantum_model(model):
        print("模型包含量子神经网络（QNN）层。")
    else:
        print("模型不包含量子神经网络（QNN）层。")

    print("\n量子电路图：")
    sample_inputs = np.ones(n_qubits, dtype=np.float32) * (np.pi / 2)  # 使用 n_qubits 个特征
    sample_rot_weights = np.zeros((n_layers, n_qubits, 3), dtype=np.float32)  # rot_weights

    print(qml.draw(quantum_neural_network_draw)(sample_inputs, sample_rot_weights))  # 使用绘制专用的 QNode

    # 早停和学习率调整回调函数
    callbacks = [
        tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True),
        tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=15)
    ]

    # **使用动态调整的 alpha 和 beta 进行重要性采样训练**

    # 创建数据集
    dataset = InMemoryDataset(
        x_train,
        y_train,
        x_test,
        y_test,
        categorical=False  # 不需要转换标签，因为已经是 one-hot 编码
    )

    # 初始化重加权策略
    reweighting_policy = BiasedReweightingPolicy(k=1.0)  # 无偏重加权策略

    # 定义总的训练步骤数，用于 beta 的线性调整
    steps_per_epoch = len(x_train) // batch_size
    total_steps = epochs * steps_per_epoch

    # 定义重要性采样训练器
    wrapped_model = ConstantTimeImportanceTraining(model, score="loss")

    # 修改采样器以使用动态的 beta 和 alpha
    def custom_sampler(dataset, batch_size, steps_per_epoch, epochs):
        return ConstantTimeSampler(
            dataset,
            reweighting_policy,
            model=wrapped_model.model,  # wrapped_model.model 是 OracleWrapper 内部的模型
            B=64,                      # 第一层随机采样的样本数
            alpha=0.6,                 # 控制优先级的参数，0 表示均匀采样，1 表示完全按照优先级采样
            beta_start=0.4,            # beta 的初始值
            beta_frames=total_steps    # beta 从 beta_start 增加到 1 所需的总步数
        )

    # 将自定义的采样器赋给 wrapped_model
    wrapped_model.sampler = custom_sampler

    # 使用重要性采样训练模型
    history = wrapped_model.fit_dataset(
        dataset=dataset,
        batch_size=batch_size,
        epochs=epochs,
        steps_per_epoch=steps_per_epoch,
        verbose=1,
        callbacks=callbacks
    )

    # 在测试集上评估模型
    score = model.evaluate(x_test, y_test, verbose=0)
    print('测试损失:', score[0])
    print('测试准确率:', score[1])

    mean_accuracy, fluctuation = calculate_accuracy_fluctuation(history)
    print('平均验证准确率:', mean_accuracy)
    print('准确率波动:', fluctuation)

    plot_history(history)

    # 关闭输出文件并恢复 stdout
    sys.stdout = original_stdout
    output_file.close()

    print("脚本执行完成。查看 'QNNImprove_Iris.txt' 获取打印输出，查看当前目录获取保存的图表。")
将上述神经网络进行修改，修改为# 定义量子神经网络层
class QuantumNeuralNetworkLayer(tf.keras.layers.Layer):
    def __init__(self, n_qubits, n_layers):
        super(QuantumNeuralNetworkLayer, self).__init__()
        self.n_qubits = n_qubits
        self.n_layers = n_layers

        # Initialize trainable weights with dtype=tf.float64
        init = tf.random_uniform_initializer(minval=0, maxval=2 * np.pi)
        self.q_weights = self.add_weight(
            shape=(n_layers, n_qubits, 3),
            initializer=init,
            trainable=True,
            name='q_weights',
            dtype=tf.float64
        )

    def call(self, inputs):
        def circuit(inputs):
            inputs = tf.cast(inputs, dtype=tf.float64)
            result = quantum_neural_network(inputs, self.q_weights)
            return result

        outputs = tf.map_fn(circuit, inputs, dtype=tf.float64)
        return outputs

# 定义量子神经网络模型
class QuantumNeuralNetwork(tf.keras.Model):
    def __init__(self, n_qubits, n_layers, num_classes):
        super(QuantumNeuralNetwork, self).__init__()
        self.n_qubits = n_qubits
        self.n_layers = n_layers
        self.num_classes = num_classes

        self.quantum_layer = QuantumNeuralNetworkLayer(n_qubits, n_layers)
        self.output_layer = Dense(num_classes, activation='softmax', dtype=tf.float64)

    def call(self, inputs):
        q_output = self.quantum_layer(inputs)
        return self.output_layer(q_output)
上述量子神经网络内容，保持其余内容不变，给我修改后的完整代码