李宏毅机器学习作业1——预测COVID-19人数

这篇博客介绍了一项使用深度学习预测COVID-19感染人数的任务。作者通过数据集划分、特征选择和模型训练,展示了如何利用TensorDataset和nn.DataParallel进行并行计算。文中提到特征选择的重要性,并探讨了模型层数对结果的影响。

目录

数据集

导包

辅助函数

设定种子

划分数据集

模型

特征选择

训练函数

配置参数

Dataloader 

开始训练

预测

预测函数

输出结果

解答

训练函数

模型

特征选择

超参数设置


数据集

训练集中给出美国某些州五天COVID-19的感染人数(及相关特征数据),测试集中给出前四天的相关数据,预测第五天的感染人数。 下载地址:ML2022Spring-hw1 | Kaggle

特征包括:
● States (37, 独热编码)
● COVID-like illness (4)
        ○ cli、ili …
● Behavior Indicators (8)
        ○ wearing_mask、travel_outside_state …
● Mental Health Indicators (3)
        ○ anxious、depressed …
● Tested Positive Cases (1)
        ○ tested_positive (this is what we want to predict)

训练集有2699行, 118列 (id + 37 states + 16 features x 5 days)
测试集有1078,117列 (without last day's positive rate)

导包

# Numerical Operations
import math
import numpy as np
from sklearn.model_selection import train_test_split

# Reading/Writing Data
import pandas as pd
import os
import csv

# For Progress Bar
from tqdm import tqdm
from d2l import torch as d2l

# Pytorch
import torch 
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader, random_split, TensorDataset

# For plotting learning curve
from torch.utils.tensorboard import SummaryWriter

辅助函数

设定种子

我的理解是,模型初始化和验证集划分都要用到seed,这里固定下来

def same_seed(seed): 
    '''Fixes random number generator seeds for reproducibility.'''
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False
    np.random.seed(seed)
    torch.manual_seed(seed)
    if torch.cuda.is_available():
        torch.cuda.manual_seed_all(seed)

划分数据集

可以使用random_split函数

def train_valid_split(data_set, valid_ratio, seed):
    '''Split provided training data into training set and validation set'''
    valid_set_size = int(valid_ratio * len(data_set)) 
    train_set_size = len(data_set) - valid_set_size
    train_set, valid_set = random_split(data_set, [train_set_size, valid_set_size],
                                        generator=torch.Generator().manual_seed(seed))
    return np.array(train_set), np.array(valid_set)

也可以采取train_test_split函数

def train_valid_split(data_set, valid_ratio, seed):
    '''Split provided training data into training set and validation set'''    
    train_set, valid_set =  train_test_split(data_set, test_size=valid_ratio, random_state=seed)
    return np.array(train_set), np.array(valid_set)

模型

整个作业最重要的就是模型和特征选择,还有超参数设置。这里放原代码

class My_Model(nn.Module):
    def __init__(self, input_dim):
        super(My_Model, self).__init__()
        # TODO: modify model's structure, be aware of dimensions. 
        self.layers = nn.Sequential(
            nn.Linear(input_dim, 16),
            nn.ReLU(),
            nn.Linear(16, 8),
            nn.ReLU(),
            nn.Linear(8, 1)
        )

    def forward(self, x):
        x = self.layers(x)
        x = x.squeeze(1) # (B, 1) -> (B)
        return x

特征选择

对原代码进行了改动,因为使用了TensorDataset,它的自变量应该是tensor,所以要把train_data, valid_data, test_data变为tensor格式

def select_feat(train_data, valid_data, test_data, select_all=True):
    '''Selects useful features to perform regression'''  
    
    train_data = torch.FloatTensor(train_data)
    valid_data = torch.FloatTensor(valid_data)
    test_data = torch.FloatTensor(test_data)
   
# coding : utf-8 ################# A SageMath implementation for the Algorithm "ClassicalToQuantum" #################### # Three instances: # 1) The AND-depth-4 and AND-count-34 classical circuit for the AES S-box (Boyar and Peralta's S-box) # 2) The AND-depth-3 and AND-count-42 classical circuit for the AES S-box introduced in Section 5.2 # 3) The AND-depth-3 and AND-count-78 classical circuit for the AES S-box introduced in ASIACRYPT 2022 # need Sagemath environment from sage.all import * """ class of quantum circuit information n : number of inputs(X) m : number of outputs(Y) s : T-depth width : number of qubits CNOT_layers_qubits : CNOT sub-circuit qubits(inputs and outputs, s+1 layers) Toffoli_layers_qubits : Toffoli layer qubits(outputs, s layers) state_transform_matrices : invertible matrices(CNOT sub-circuits) """ class quantum_circuit_info(): def __init__(self, n: int, m: int, s: int, width: int , CNOT_layers_qubits: list , Toffoli_layers_qubits: list , state_transform_matrices: list): self.n = n self.m = m self.s = s self.width = width self.CNOT_layers_qubits = CNOT_layers_qubits self.Toffoli_layers_qubits = Toffoli_layers_qubits self.state_transform_matrices = state_transform_matrices def print_Info(self, save_file_name="info.txt"): print_rows = ["# quantum circuit information"] print_rows.append(f"# n: {self.n}") print_rows.append(f"# m: {self.n}") print_rows.append(f"# T-depth: {self.s}") print_rows.append(f"# width: {self.width}") print_rows.append(f"#########################") # print_rows.append(f"# ") for i in range(self.s): print_rows.append(f"# CNOT sub-circuit-{i+1}") print_rows.append(f"# CNOT inputs qubits({len(self.CNOT_layers_qubits[i])}):") print_rows.append(f"{self.CNOT_layers_qubits[i]}") print_rows.append(f"# state transform matrix:") print_rows.append(f"{self.state_transform_matrices[i]}") ni = len(self.Toffoli_layers_qubits[i]) print_rows.append(f"# Toffoli inputs qubits({2*ni}):") print_rows.append(f"{self.CNOT_layers_qubits[i][:2*ni]}") print_rows.append(f"# Toffoli outputs qubits({ni}):") print_rows.append(f"{self.Toffoli_layers_qubits[i]}") print_rows.append(f"#########################") print_rows.append(f"# CNOT sub-circuit-Y") print_rows.append(f"# CNOT inputs qubits:") print_rows.append(f"{self.CNOT_layers_qubits[-1]}") print_rows.append(f"# state transform matrix:") print_rows.append(f"{self.state_transform_matrices[-1]}") print_rows.append(f"# outputs qubits:") print_rows.append(f"{self.CNOT_layers_qubits[-1][:self.m]}") print("\n".join(print_rows)) with open(save_file_name, "w") as f: f.write("\n".join(print_rows)) ######################### auxiliary functions ######################### # matrix over GF(2) def matGF2(mat_list: list): return matrix(GF(2), mat_list) # L : linear expressions # n : number of "X"(initial inputs) # cols : number of columns(variables) # return ML: the matrix of linear expressions L(over GF(2)) # variables are "X"(initial inputs) and "M"(outputs of AND gates) def linears_to_matrix(L: list, n: int, cols: int): rows = len(L) vars_to_idx = {} for i in range(n): vars_to_idx[f"X{i}"] = i for i in range(cols-n): vars_to_idx[f"M{i}"] = n+i ML = [[0 for j in range(cols)] for i in range(rows)] for i in range(rows): rights = L[i].split("+") for j in rights: if j not in vars_to_idx.keys(): continue ML[i][vars_to_idx[j]] = 1 return matGF2(ML) def get_AES_Sbox_ANF(X): X0 = X[0] X1 = X[1] X2 = X[2] X3 = X[3] X4 = X[4] X5 = X[5] X6 = X[6] X7 = X[7] # 1005 XOR return[ X0*X1*X2*X3*X4*X5*X7 + X0*X1*X2*X3*X4*X6*X7 + X0*X1*X2*X3*X4*X6 + X0*X1*X2*X3*X4 + X0*X1*X2*X3*X5*X7 + X0*X1*X2*X3*X6 + X0*X1*X2*X3 + X0*X1*X2*X4*X5*X7 + X0*X1*X2*X4*X6*X7 + X0*X1*X2*X4*X6 + X0*X1*X2*X4*X7 + X0*X1*X2*X5*X6 + X0*X1*X2*X5 + X0*X1*X2*X6*X7 + X0*X1*X2*X6 + X0*X1*X3*X4*X5*X7 + X0*X1*X3*X4*X6*X7 + X0*X1*X3*X4*X7 + X0*X1*X3*X5*X6 + X0*X1*X3*X5 + X0*X1*X3*X6 + X0*X1*X3*X7 + X0*X1*X3 + X0*X1*X4*X5*X6*X7 + X0*X1*X4*X6 + X0*X1*X4*X7 + X0*X1*X5*X6*X7 + X0*X1*X5 + X0*X1*X7 + X0*X2*X3*X4*X5*X7 + X0*X2*X3*X4*X6*X7 + X0*X2*X3*X5*X6*X7 + X0*X2*X3*X6*X7 + X0*X2*X3*X6 + X0*X2*X3 + X0*X2*X4*X7 + X0*X2*X4 + X0*X2*X5*X6*X7 + X0*X2 + X0*X3*X4*X5*X7 + X0*X3*X4*X5 + X0*X3*X4*X6*X7 + X0*X3*X4*X6 + X0*X3*X4*X7 + X0*X3*X5*X7 + X0*X3*X6*X7 + X0*X3*X6 + X0*X4*X5*X6 + X0*X4*X5*X7 + X0*X4*X7 + X0*X5*X6*X7 + X0*X5*X7 + X0*X6 + X0*X7 + X0 + X1*X2*X3*X4*X5*X7 + X1*X2*X3*X4*X5 + X1*X2*X3*X4*X7 + X1*X2*X3*X4 + X1*X2*X3*X5*X6 + X1*X2*X3*X6*X7 + X1*X2*X3 + X1*X2*X4*X5*X6 + X1*X2*X4*X7 + X1*X2*X5 + X1*X2*X7 + X1*X3*X4*X5*X7 + X1*X3*X4*X7 + X1*X3*X5*X6*X7 + X1*X3*X5*X6 + X1*X3*X5*X7 + X1*X3*X5 + X1*X3 + X1*X4*X5*X6*X7 + X1*X4*X5*X7 + X1*X4*X6*X7 + X1*X4*X6 + X1*X4*X7 + X1*X5*X6 + X1*X5 + X1*X6*X7 + X1*X7 + X2*X3*X4*X5*X7 + X2*X3*X4*X6*X7 + X2*X3*X4 + X2*X3*X5*X6 + X2*X3*X7 + X2*X4*X5*X6 + X2*X4*X5*X7 + X2*X4*X5 + X2*X4*X6 + X2*X4*X7 + X2*X4 + X2*X5*X6*X7 + X2*X5*X7 + X2*X6*X7 + X2 + X3*X4*X5*X6*X7 + X3*X4*X5*X6 + X3*X4*X6*X7 + X3*X5*X6*X7 + X3*X5 + X3*X6*X7 + X3 + X4*X5*X6*X7 + X4*X5*X6 + X4*X5*X7 + X5*X6 + X5*X7 + X5 ,X0*X1*X2*X3*X4*X6*X7 + X0*X1*X2*X3*X4*X6 + X0*X1*X2*X3*X4 + X0*X1*X2*X3*X5*X6*X7 + X0*X1*X2*X3*X6 + X0*X1*X2*X3*X7 + X0*X1*X2*X3 + X0*X1*X2*X4*X5 + X0*X1*X2*X4*X6*X7 + X0*X1*X2*X5*X6*X7 + X0*X1*X2*X5*X6 + X0*X1*X2*X5*X7 + X0*X1*X2 + X0*X1*X3*X4*X5 + X0*X1*X3*X4*X6*X7 + X0*X1*X3*X4*X7 + X0*X1*X3*X5*X6*X7 + X0*X1*X3*X5*X7 + X0*X1*X3*X6 + X0*X1*X3*X7 + X0*X1*X4*X6*X7 + X0*X1*X4 + X0*X1*X6*X7 + X0*X1*X6 + X0*X2*X3*X4*X6*X7 + X0*X2*X3*X4*X6 + X0*X2*X3*X5*X6*X7 + X0*X2*X3*X5*X7 + X0*X2*X3*X6*X7 + X0*X2*X3*X6 + X0*X2*X4*X5*X6 + X0*X2*X4*X5 + X0*X2*X4*X6 + X0*X2*X4 + X0*X2*X5*X7 + X0*X2*X7 + X0*X2 + X0*X3*X4*X5*X6*X7 + X0*X3*X4*X5*X6 + X0*X3*X4*X6 + X0*X3*X4*X7 + X0*X3*X5*X6 + X0*X3*X5 + X0*X3*X6 + X0*X3*X7 + X0*X4*X5*X6*X7 + X0*X4*X5*X7 + X0*X4*X5 + X0*X4*X6*X7 + X0*X4 + X0*X5*X6 + X0*X6 + X0*X7 + X1*X2*X3*X4*X6*X7 + X1*X2*X3*X4*X6 + X1*X2*X3*X5*X6*X7 + X1*X2*X3*X5*X6 + X1*X2*X3 + X1*X2*X4*X5*X6*X7 + X1*X2*X4*X5*X7 + X1*X2*X4*X5 + X1*X2*X6 + X1*X2*X7 + X1*X3*X4*X5 + X1*X3*X4*X6 + X1*X3*X4*X7 + X1*X3*X4 + X1*X3*X5*X7 + X1*X3*X5 + X1*X3*X6 + X1*X3*X7 + X1*X3 + X1*X4*X5*X6*X7 + X1*X4*X5*X6 + X1*X4*X5*X7 + X1*X4*X6*X7 + X1*X4*X6 + X1*X4*X7 + X1*X5*X6 + X1*X5*X7 + X1 + X2*X3*X4*X5*X6 + X2*X3*X4*X5*X7 + X2*X3*X4*X5 + X2*X3*X4*X6*X7 + X2*X3*X5*X6 + X2*X3*X5*X7 + X2*X3*X7 + X2*X4*X5*X6*X7 + X2*X4*X5*X6 + X2*X4*X5*X7 + X2*X4*X7 + X2*X4 + X2*X5*X6*X7 + X2*X5*X6 + X2*X5*X7 + X2*X6*X7 + X2*X7 + X2 + X3*X4*X5*X6 + X3*X4*X5 + X3*X4*X6*X7 + X3*X4*X6 + X3*X5*X6*X7 + X3*X5*X7 + X3*X6*X7 + X3*X7 + X4*X5 + X4*X6*X7 + X4*X6 + X4 + 1 ,X0*X1*X2*X3*X5*X6*X7 + X0*X1*X2*X3*X5*X6 + X0*X1*X2*X3*X5*X7 + X0*X1*X2*X3*X5 + X0*X1*X2*X3*X7 + X0*X1*X2*X4*X5*X6*X7 + X0*X1*X2*X4*X5*X6 + X0*X1*X2*X4*X5 + X0*X1*X2*X6*X7 + X0*X1*X2*X7 + X0*X1*X2 + X0*X1*X3*X4*X5*X6 + X0*X1*X3*X4*X5*X7 + X0*X1*X3*X4*X6 + X0*X1*X3*X4 + X0*X1*X3*X6*X7 + X0*X1*X3*X7 + X0*X1*X4*X5*X7 + X0*X1*X5*X6*X7 + X0*X1*X5 + X0*X1*X6 + X0*X2*X3*X4*X7 + X0*X2*X3*X4 + X0*X2*X3*X5*X6*X7 + X0*X2*X3*X5*X7 + X0*X2*X3*X5 + X0*X2*X3*X6*X7 + X0*X2*X3*X6 + X0*X2*X3 + X0*X2*X4*X5*X6*X7 + X0*X2*X4*X5*X7 + X0*X2*X4*X6*X7 + X0*X2*X4*X6 + X0*X2*X4 + X0*X2*X5 + X0*X2*X6*X7 + X0*X2*X6 + X0*X2 + X0*X3*X4*X5*X6 + X0*X3*X4*X5*X7 + X0*X3*X4*X5 + X0*X3*X4*X6 + X0*X3*X4*X7 + X0*X3*X5*X6 + X0*X3*X5 + X0*X3*X6*X7 + X0*X3*X6 + X0*X3*X7 + X0*X4*X5*X6*X7 + X0*X4*X5 + X0*X4*X6*X7 + X0*X4*X6 + X0*X6*X7 + X0 + X1*X2*X3*X4*X5 + X1*X2*X3*X4*X6 + X1*X2*X3*X4*X7 + X1*X2*X3*X4 + X1*X2*X3*X5*X6*X7 + X1*X2*X3*X5*X6 + X1*X2*X3*X6*X7 + X1*X2*X4*X5*X6*X7 + X1*X2*X4*X5 + X1*X2*X4*X6 + X1*X2*X6*X7 + X1*X2*X6 + X1*X2*X7 + X1*X3*X4*X5*X6 + X1*X3*X4*X5 + X1*X3*X4*X6 + X1*X3*X5*X6*X7 + X1*X3*X6*X7 + X1*X3*X6 + X1*X3 + X1*X4*X5*X6*X7 + X1*X4*X5*X6 + X1*X4*X5 + X1*X4*X6 + X1*X5*X6 + X1*X5*X7 + X1*X6*X7 + X1*X6 + X1 + X2*X3*X4*X5*X6*X7 + X2*X3*X4*X5 + X2*X3*X4 + X2*X3*X5*X6*X7 + X2*X3*X5*X6 + X2*X3*X5*X7 + X2*X3*X5 + X2*X3*X6*X7 + X2*X4*X5*X6*X7 + X2*X4*X5*X6 + X2*X4*X5*X7 + X2*X4*X6 + X2*X4*X7 + X2*X5*X6*X7 + X2*X5*X6 + X2*X6*X7 + X2*X6 + X3*X4*X5*X6*X7 + X3*X4*X5*X7 + X3*X4*X6*X7 + X3*X4 + X3*X5*X6 + X3*X5*X7 + X3*X5 + X3*X6*X7 + X3 + X4*X5*X6*X7 + X4*X6*X7 + X4*X7 + X5*X6*X7 + 1 ,X0*X1*X2*X3*X4*X5*X7 + X0*X1*X2*X3*X4*X7 + X0*X1*X2*X3*X4 + X0*X1*X2*X3*X5 + X0*X1*X2*X3*X6 + X0*X1*X2*X3*X7 + X0*X1*X2*X4*X5*X6 + X0*X1*X2*X4*X6 + X0*X1*X2*X4*X7 + X0*X1*X2*X4 + X0*X1*X2*X5*X6 + X0*X1*X2*X6*X7 + X0*X1*X2 + X0*X1*X3*X4*X5*X6*X7 + X0*X1*X3*X4*X5*X6 + X0*X1*X3*X4*X5 + X0*X1*X3*X4*X7 + X0*X1*X3*X4 + X0*X1*X3*X5*X7 + X0*X1*X3*X7 + X0*X1*X3 + X0*X1*X4*X6*X7 + X0*X1*X4*X7 + X0*X1*X4 + X0*X1*X5*X6*X7 + X0*X1*X5*X6 + X0*X1*X5 + X0*X1*X6*X7 + X0*X1*X6 + X0*X1*X7 + X0*X1 + X0*X2*X3*X4*X5*X6*X7 + X0*X2*X3*X4*X5*X6 + X0*X2*X3*X4*X5*X7 + X0*X2*X3*X4*X5 + X0*X2*X3*X4*X6 + X0*X2*X3*X4 + X0*X2*X3*X5*X6 + X0*X2*X3 + X0*X2*X4*X5*X6 + X0*X2*X4*X5*X7 + X0*X2*X4*X5 + X0*X2*X4*X6*X7 + X0*X2*X4*X7 + X0*X2*X4 + X0*X2*X5*X6*X7 + X0*X2*X5*X6 + X0*X2*X5*X7 + X0*X2*X6*X7 + X0*X2*X6 + X0*X2 + X0*X3*X4*X5*X6*X7 + X0*X3*X4*X5 + X0*X3*X4*X6 + X0*X3*X4*X7 + X0*X3*X4 + X0*X3*X5 + X0*X3*X7 + X0*X4*X5*X6*X7 + X0*X4*X6*X7 + X0*X4 + X0*X7 + X1*X2*X3*X4*X5*X6 + X1*X2*X3*X4*X5 + X1*X2*X3*X4*X6*X7 + X1*X2*X3*X4*X6 + X1*X2*X3*X5*X6*X7 + X1*X2*X3*X5*X6 + X1*X2*X3*X6*X7 + X1*X2*X3*X7 + X1*X2*X4*X5*X6*X7 + X1*X2*X4*X5*X6 + X1*X2*X4*X5*X7 + X1*X2*X4*X5 + X1*X2*X4*X7 + X1*X2*X4 + X1*X2*X5 + X1*X2*X6*X7 + X1*X3*X4*X5*X6 + X1*X3*X4*X5*X7 + X1*X3*X4*X6*X7 + X1*X3*X4*X7 + X1*X3*X4 + X1*X3*X5*X6 + X1*X3*X6*X7 + X1*X3*X7 + X1*X3 + X1*X4*X5*X6*X7 + X1*X4*X5 + X1*X4*X6*X7 + X1*X5*X6*X7 + X1*X5*X7 + X1*X6 + X1*X7 + X2*X3*X4*X5*X6*X7 + X2*X3*X4*X5*X7 + X2*X3*X4*X6 + X2*X3*X4*X7 + X2*X3*X4 + X2*X3*X5*X6*X7 + X2*X3*X5 + X2*X3*X6*X7 + X2*X3*X6 + X2*X3*X7 + X2*X3 + X2*X4*X5*X6 + X2*X4*X5 + X2*X4*X6*X7 + X2*X4*X6 + X2*X4*X7 + X2*X4 + X2*X5 + X2*X6 + X2*X7 + X2 + X3*X4*X5*X6*X7 + X3*X4*X5*X6 + X3*X4*X5*X7 + X3*X4*X5 + X3*X4*X7 + X3*X4 + X3*X5*X6 + X3*X6 + X3*X7 + X4*X5*X7 + X4*X5 + X4 + X5 + X6*X7 + X6 + X7 ,X0*X1*X2*X3*X4*X5*X7 + X0*X1*X2*X3*X4*X5 + X0*X1*X2*X3*X4*X6*X7 + X0*X1*X2*X3*X4 + X0*X1*X2*X3*X5*X6 + X0*X1*X2*X3*X5 + X0*X1*X2*X3*X7 + X0*X1*X2*X3 + X0*X1*X2*X4*X5*X6*X7 + X0*X1*X2*X4*X5*X6 + X0*X1*X2*X4*X5*X7 + X0*X1*X2*X4*X6 + X0*X1*X2*X4*X7 + X0*X1*X2*X4 + X0*X1*X2*X5*X6 + X0*X1*X2*X5*X7 + X0*X1*X2*X6*X7 + X0*X1*X2*X6 + X0*X1*X2*X7 + X0*X1*X2 + X0*X1*X3*X4*X5*X6 + X0*X1*X3*X4*X5*X7 + X0*X1*X3*X4*X6 + X0*X1*X3*X4*X7 + X0*X1*X3*X4 + X0*X1*X3*X5*X6*X7 + X0*X1*X3*X5 + X0*X1*X3*X6 + X0*X1*X4*X5*X6*X7 + X0*X1*X4*X5*X7 + X0*X1*X4*X7 + X0*X1*X5*X6 + X0*X1*X5*X7 + X0*X1*X6*X7 + X0*X1 + X0*X2*X3*X4*X5*X6 + X0*X2*X3*X4*X5 + X0*X2*X3*X4*X6*X7 + X0*X2*X3*X4*X7 + X0*X2*X3*X5*X6*X7 + X0*X2*X3*X5*X7 + X0*X2*X3*X5 + X0*X2*X3*X6*X7 + X0*X2*X3*X6 + X0*X2*X4*X5*X6*X7 + X0*X2*X4*X5*X7 + X0*X2*X4 + X0*X2*X5*X6*X7 + X0*X2*X5*X6 + X0*X2*X5 + X0*X2 + X0*X3*X4*X5*X6*X7 + X0*X3*X4*X6*X7 + X0*X3*X4*X6 + X0*X3*X4 + X0*X3*X5*X6*X7 + X0*X3*X5*X6 + X0*X3*X5*X7 + X0*X4*X5*X6*X7 + X0*X4*X5*X6 + X0*X4*X5*X7 + X0*X4*X5 + X0*X4*X7 + X0*X4 + X0*X5*X6 + X0*X5*X7 + X0*X6*X7 + X0*X6 + X0*X7 + X0 + X1*X2*X3*X4*X5*X6*X7 + X1*X2*X3*X4*X5*X6 + X1*X2*X3*X4*X5 + X1*X2*X3*X4*X7 + X1*X2*X3*X4 + X1*X2*X3*X5*X6 + X1*X2*X3*X5*X7 + X1*X2*X3*X5 + X1*X2*X3*X6 + X1*X2*X3*X7 + X1*X2*X4*X5*X6*X7 + X1*X2*X4*X5*X6 + X1*X2*X4*X5*X7 + X1*X2*X4*X6*X7 + X1*X2*X4*X6 + X1*X2*X4 + X1*X2*X5*X6*X7 + X1*X2*X5*X7 + X1*X2*X6*X7 + X1*X2*X6 + X1*X2 + X1*X3*X4*X5*X6*X7 + X1*X3*X4*X6*X7 + X1*X3*X4*X6 + X1*X3*X4*X7 + X1*X3*X5*X7 + X1*X3*X6*X7 + X1*X3*X7 + X1*X4*X5*X6 + X1*X4*X6*X7 + X1*X4*X7 + X1*X4 + X1*X5*X6 + X1*X5*X7 + X1*X6*X7 + X1 + X2*X3*X4*X5*X7 + X2*X3*X4*X5 + X2*X3*X4*X6*X7 + X2*X3*X4*X6 + X2*X3*X5*X6 + X2*X3*X5 + X2*X3*X7 + X2*X3 + X2*X4*X5*X6*X7 + X2*X4*X5*X6 + X2*X4*X5 + X2*X5*X6*X7 + X2*X5*X6 + X2*X6*X7 + X3*X4*X5*X6*X7 + X3*X4*X5 + X3*X4*X6 + X3*X4*X7 + X3*X5*X6*X7 + X3*X5*X7 + X3*X6*X7 + X3 + X4*X5*X6*X7 + X4*X5*X6 + X4*X5*X7 + X4*X5 + X4*X6*X7 + X4*X7 + X5*X6 + X7 ,X0*X1*X2*X3*X4*X5*X6 + X0*X1*X2*X3*X4*X5*X7 + X0*X1*X2*X3*X4*X5 + X0*X1*X2*X3*X4*X6*X7 + X0*X1*X2*X3*X4 + X0*X1*X2*X3*X5*X7 + X0*X1*X2*X3*X6*X7 + X0*X1*X2*X3*X6 + X0*X1*X2*X3*X7 + X0*X1*X2*X3 + X0*X1*X2*X4*X5*X6*X7 + X0*X1*X2*X4*X5*X7 + X0*X1*X2*X4*X5 + X0*X1*X2*X4*X6*X7 + X0*X1*X2*X4*X6 + X0*X1*X2*X4*X7 + X0*X1*X2*X4 + X0*X1*X2*X5*X7 + X0*X1*X2*X5 + X0*X1*X2*X6 + X0*X1*X3*X4*X5*X6*X7 + X0*X1*X3*X4*X5*X6 + X0*X1*X3*X4*X5*X7 + X0*X1*X3*X4*X5 + X0*X1*X3*X4*X6*X7 + X0*X1*X3*X4*X6 + X0*X1*X3*X5*X6 + X0*X1*X3*X5 + X0*X1*X3*X6*X7 + X0*X1*X3 + X0*X1*X4*X5*X6 + X0*X1*X4*X6*X7 + X0*X1*X4*X6 + X0*X1*X5*X6*X7 + X0*X1*X5*X6 + X0*X1*X5 + X0*X1*X7 + X0*X1 + X0*X2*X3*X4*X5*X6 + X0*X2*X3*X4*X6*X7 + X0*X2*X3*X4*X7 + X0*X2*X3*X5*X6*X7 + X0*X2*X3*X5*X6 + X0*X2*X3*X5 + X0*X2*X3*X6 + X0*X2*X4*X5*X7 + X0*X2*X4*X6*X7 + X0*X2*X4*X6 + X0*X2*X4*X7 + X0*X2*X5*X6*X7 + X0*X2*X5*X6 + X0*X2*X6 + X0*X2*X7 + X0*X3*X4*X5*X6*X7 + X0*X3*X4*X5*X7 + X0*X3*X4*X5 + X0*X3*X4*X6*X7 + X0*X3*X4*X7 + X0*X3*X5*X6*X7 + X0*X3*X5*X6 + X0*X3*X5*X7 + X0*X3*X6 + X0*X3*X7 + X0*X3 + X0*X4*X5*X6*X7 + X0*X4*X5*X7 + X0*X4*X5 + X0*X4*X6 + X0*X4*X7 + X0*X5*X6*X7 + X0*X5*X6 + X0*X5*X7 + X0*X6*X7 + X0*X7 + X0 + X1*X2*X3*X4*X5*X7 + X1*X2*X3*X4*X6 + X1*X2*X3*X4 + X1*X2*X3*X5*X6*X7 + X1*X2*X3*X5*X6 + X1*X2*X3*X5*X7 + X1*X2*X3*X5 + X1*X2*X4*X5*X6*X7 + X1*X2*X4*X5 + X1*X2*X4*X7 + X1*X2*X4 + X1*X2*X6*X7 + X1*X2*X6 + X1*X2 + X1*X3*X4*X5*X6*X7 + X1*X3*X4*X5*X6 + X1*X3*X4*X5 + X1*X3*X4*X6*X7 + X1*X3*X4*X7 + X1*X3*X4 + X1*X3*X5*X7 + X1*X3*X6*X7 + X1*X3*X7 + X1*X4*X5*X6*X7 + X1*X4*X5*X6 + X1*X4*X5 + X1*X4*X6*X7 + X1*X4*X7 + X1*X5*X6 + X1*X6*X7 + X1*X7 + X2*X3*X4*X5*X6*X7 + X2*X3*X4*X5*X6 + X2*X3*X4 + X2*X3*X5*X6*X7 + X2*X3*X5 + X2*X3*X6 + X2*X3*X7 + X2*X4*X5*X6*X7 + X2*X4*X5*X6 + X2*X4*X5*X7 + X2*X4*X6*X7 + X2*X4*X6 + X2*X4*X7 + X2*X5*X6*X7 + X2*X5*X6 + X2*X5*X7 + X2*X6*X7 + X2*X7 + X2 + X3*X4*X5*X6*X7 + X3*X4*X5*X6 + X3*X4*X5*X7 + X3*X4*X5 + X3*X4*X6*X7 + X3*X4*X6 + X3*X4*X7 + X3*X4 + X3*X5*X6 + X3*X5*X7 + X3*X6*X7 + X3*X6 + X3*X7 + X4*X5*X7 + X4*X5 + X4*X6*X7 + X4*X7 + X5*X7 + X6 + X7 ,X0*X1*X2*X3*X4*X6*X7 + X0*X1*X2*X3*X4*X6 + X0*X1*X2*X3*X5*X6*X7 + X0*X1*X2*X3*X5 + X0*X1*X2*X3*X7 + X0*X1*X2*X4*X5*X6*X7 + X0*X1*X2*X4*X5*X6 + X0*X1*X2*X4*X5*X7 + X0*X1*X2*X4*X7 + X0*X1*X2*X5*X6*X7 + X0*X1*X2*X5*X7 + X0*X1*X2*X6*X7 + X0*X1*X2 + X0*X1*X3*X4*X5*X6*X7 + X0*X1*X3*X4*X5*X6 + X0*X1*X3*X4*X5*X7 + X0*X1*X3*X4*X5 + X0*X1*X3*X4*X6*X7 + X0*X1*X3*X4 + X0*X1*X3*X5*X6*X7 + X0*X1*X3*X5 + X0*X1*X3*X6 + X0*X1*X3*X7 + X0*X1*X4*X5*X7 + X0*X1*X4*X6*X7 + X0*X1*X4 + X0*X1*X5*X6*X7 + X0*X1*X5*X6 + X0*X1*X5 + X0*X1*X7 + X0*X2*X3*X4*X5*X7 + X0*X2*X3*X4*X5 + X0*X2*X3*X4*X6 + X0*X2*X3*X4*X7 + X0*X2*X3*X4 + X0*X2*X3*X5*X6*X7 + X0*X2*X3*X5 + X0*X2*X3*X6 + X0*X2*X3*X7 + X0*X2*X4*X5*X7 + X0*X2*X4*X5 + X0*X2*X4*X6*X7 + X0*X2*X4*X6 + X0*X2*X4 + X0*X2*X5*X6 + X0*X2*X5 + X0*X2*X7 + X0*X3*X4*X5*X6*X7 + X0*X3*X4*X5*X7 + X0*X3*X4*X5 + X0*X3*X4*X6*X7 + X0*X3*X4*X6 + X0*X3*X4 + X0*X3*X5*X6*X7 + X0*X3*X5*X6 + X0*X3*X6*X7 + X0*X3*X6 + X0*X3*X7 + X0*X4*X5*X6*X7 + X0*X4*X6 + X0*X4 + X0*X5*X7 + X0*X5 + X0*X6 + X0*X7 + X0 + X1*X2*X3*X4*X5*X6 + X1*X2*X3*X4*X5*X7 + X1*X2*X3*X4*X6*X7 + X1*X2*X3*X4*X6 + X1*X2*X3*X4 + X1*X2*X3*X5*X6 + X1*X2*X3*X5*X7 + X1*X2*X3*X6*X7 + X1*X2*X3*X7 + X1*X2*X4*X5*X6*X7 + X1*X2*X4*X5*X6 + X1*X2*X4*X6*X7 + X1*X2*X4*X6 + X1*X2*X4 + X1*X2*X5*X6 + X1*X2*X5*X7 + X1*X2*X6*X7 + X1*X2*X6 + X1*X3*X4*X5*X6*X7 + X1*X3*X4*X5*X6 + X1*X3*X4*X6*X7 + X1*X3*X4*X7 + X1*X3*X4 + X1*X3*X5*X6*X7 + X1*X3*X7 + X1*X3 + X1*X4*X5*X6*X7 + X1*X4*X5*X7 + X1*X4*X5 + X1*X4*X6*X7 + X1*X4*X6 + X1*X5*X6*X7 + X1*X5 + X1*X6*X7 + X1 + X2*X3*X4*X5*X7 + X2*X3*X4*X5 + X2*X3*X4*X6 + X2*X3*X4*X7 + X2*X3*X5*X6*X7 + X2*X3*X5*X7 + X2*X3*X6*X7 + X2*X3*X7 + X2*X3 + X2*X4*X5*X6 + X2*X4*X5 + X2*X4*X6 + X3*X4*X5*X6*X7 + X3*X4*X5 + X3*X4*X6*X7 + X3*X4*X6 + X3*X4*X7 + X3*X5*X6 + X3*X6*X7 + X3*X6 + X3*X7 + X4*X5*X6 + X4*X5*X7 + X4*X5 + X4*X6*X7 + X4*X6 + X4*X7 + X4 + X5*X7 + X6*X7 + X7 + 1 ,X0*X1*X2*X3*X4*X5*X7 + X0*X1*X2*X3*X4*X5 + X0*X1*X2*X3*X4*X6 + X0*X1*X2*X3*X5*X6*X7 + X0*X1*X2*X3*X5*X6 + X0*X1*X2*X3*X5 + X0*X1*X2*X3*X6*X7 + X0*X1*X2*X4*X5*X7 + X0*X1*X2*X4*X7 + X0*X1*X2*X4 + X0*X1*X2*X5*X6*X7 + X0*X1*X2*X5*X6 + X0*X1*X2*X6*X7 + X0*X1*X2*X6 + X0*X1*X2 + X0*X1*X3*X4*X5*X6*X7 + X0*X1*X3*X4*X5*X6 + X0*X1*X3*X4*X6*X7 + X0*X1*X3*X4*X7 + X0*X1*X3*X5*X6 + X0*X1*X3*X5*X7 + X0*X1*X3*X7 + X0*X1*X4*X5*X6*X7 + X0*X1*X4*X5 + X0*X1*X4*X6*X7 + X0*X1*X4*X6 + X0*X1*X4 + X0*X1*X5*X6*X7 + X0*X1*X5*X6 + X0*X1*X5 + X0*X1 + X0*X2*X3*X4*X5*X7 + X0*X2*X3*X4*X5 + X0*X2*X3*X4*X6 + X0*X2*X3*X4*X7 + X0*X2*X3*X5*X6*X7 + X0*X2*X3*X5*X7 + X0*X2*X3*X5 + X0*X2*X3*X6 + X0*X2*X3*X7 + X0*X2*X4*X5*X6*X7 + X0*X2*X4*X5*X6 + X0*X2*X4*X5 + X0*X2*X4*X6*X7 + X0*X2*X4 + X0*X2*X5*X6*X7 + X0*X2*X5*X7 + X0*X2*X5 + X0*X2 + X0*X3*X4*X5*X6*X7 + X0*X3*X4 + X0*X3*X5*X6*X7 + X0*X3*X5*X6 + X0*X3*X5*X7 + X0*X3*X5 + X0*X3*X6*X7 + X0*X3*X7 + X0*X4*X5*X6*X7 + X0*X4*X5*X6 + X0*X4*X5*X7 + X0*X4*X5 + X0*X4*X6 + X0*X5*X6*X7 + X0*X5*X7 + X0*X5 + X0*X6*X7 + X1*X2*X3*X4*X5*X7 + X1*X2*X3*X4*X5 + X1*X2*X3*X4*X7 + X1*X2*X3*X5*X6 + X1*X2*X3*X5 + X1*X2*X3*X6*X7 + X1*X2*X3*X6 + X1*X2*X3*X7 + X1*X2*X3 + X1*X2*X4*X5*X6 + X1*X2*X4*X6*X7 + X1*X2*X4*X7 + X1*X2*X5*X7 + X1*X2*X5 + X1*X2*X6 + X1*X2 + X1*X3*X4*X5*X6*X7 + X1*X3*X4*X5*X7 + X1*X3*X4*X6*X7 + X1*X3*X4*X6 + X1*X3*X4*X7 + X1*X3*X5*X6 + X1*X3*X6 + X1*X3*X7 + X1*X3 + X1*X4*X5*X6*X7 + X1*X4*X5*X6 + X1*X4*X7 + X1*X5*X6*X7 + X1*X5*X6 + X1*X5*X7 + X1*X5 + X1*X6*X7 + X1*X6 + X1*X7 + X2*X3*X4*X5*X7 + X2*X3*X5*X6*X7 + X2*X3*X5*X7 + X2*X3*X6*X7 + X2*X4*X5*X6 + X2*X4*X5*X7 + X2*X4*X5 + X2*X4*X7 + X2*X5*X6*X7 + X2*X5*X7 + X2*X7 + X3*X4*X5*X6*X7 + X3*X4*X6 + X3*X4*X7 + X3*X5*X6 + X3*X5*X7 + X3*X5 + X3*X6*X7 + X3*X6 + X3*X7 + X3 + X4*X5*X6*X7 + X4*X5*X6 + X4*X5 + X4*X6 + X4 + X5*X6 + X5 + X6*X7 + X7 + 1] ######################### main functions ######################### # C: classical circuit (uses {NOT, XOR, AND}) # inputs must be "X", outputs must be "Y" # C has 3 kinds of operations # (1) NOT: "X=X+1" # (2) XOR: "X0=X1+X2" # (3) AND: "X0=X1*X2" # return the affine expressions (the inputs of AND gates and outputs Y) # affine(X, M), M is AND variable # k AND gates have 2k inputs def extract_linear(n: int, m: int, C: list): AND_vars = [] AND_vars_inputs = {} # AND-depth vars_AND_depth = {"1" : 0} for i in range(n): vars_AND_depth[f"X{i}"] = 0 AND_depth = 0 # AND_vars depth layer AND_depth_layer = [[]] # extract all AND-variable in classical circuit for oper in C: oper = oper.replace(" ", "") x1 = oper.split("=")[0] # "x1=x2+x3" if "+" in oper: x2 = oper.split("=")[1].split("+")[0] x3 = oper.split("=")[1].split("+")[1] vars_AND_depth[x1] = max(vars_AND_depth[x2], vars_AND_depth[x3]) # "x1=x2*x3" if "*" in oper: x2 = oper.split("=")[1].split("*")[0] x3 = oper.split("=")[1].split("*")[1] AND_vars.append(x1) AND_vars_inputs[x1] = [x2, x3] # AND depth +1 vars_AND_depth[x1] = 1+max(vars_AND_depth[x2], vars_AND_depth[x3]) if vars_AND_depth[x1] > AND_depth: AND_depth = vars_AND_depth[x1] AND_depth_layer.append([]) AND_depth_layer[vars_AND_depth[x1]].append(x1) AND_vars_n = len(AND_vars) # Set inputs(X) and AND-variable(M) as a basis # Others can be expressed as affine(X, M) # variable over Boolean Polynomial Ring BV_vars_str = ["1"] + [f"X{i}" for i in range(n)] + [f"M{i}" for i in range(AND_vars_n)] BV = BooleanPolynomialRing(names=BV_vars_str[1:]) # map all strings to Boolean variables vars_to_BV_maps = {"1" : 1} for i in range(n): vars_to_BV_maps[f"X{i}"] = BV(f"X{i}") # need to reorder the seq of AND-vars # eg: the AND_vars[i] maybe not the M[i] # get the AND vars real seq [[AND-1], [AND-2], [AND-s]] idx = 0 for d in range(1, AND_depth+1): AND_vars_d = AND_depth_layer[d] for v in AND_vars_d: vars_to_BV_maps[v] = BV(f"M{idx}") idx += 1 # convert to Boolean operation(XOR) for oper in C: oper = oper.replace(" ", "") if "*" in oper: continue # "x1=x2+x3" x1 = oper.split("=")[0] x2 = oper.split("=")[1].split("+")[0] x3 = oper.split("=")[1].split("+")[1] x2_v = vars_to_BV_maps[x2] x3_v = vars_to_BV_maps[x3] vars_to_BV_maps[x1] = x2_v + x3_v # 1+s+1 layer(initial inputs + s-layer + outputs Y) # L = [[X(x)], [AND-1-L], [AND-2-L],..., [AND-s-L], [Y]] L = [[] for i in range(AND_depth+2)] L[0] = [f"X{i}" for i in range(n)] for d in range(1, AND_depth+1): AND_vars_d = AND_depth_layer[d] for v in AND_vars_d: x1 = AND_vars_inputs[v][0] x2 = AND_vars_inputs[v][1] L[d].append(f"{vars_to_BV_maps[x1]}".replace(" ", "")) L[d].append(f"{vars_to_BV_maps[x2]}".replace(" ", "")) for i in range(m): L[-1].append(f"{vars_to_BV_maps[f'Y{i}']}".replace(" ", "")) return L # zero states : can be restored to "0" only use CNOT gates # nonzero states : cannot be restored to "0" only use CNOT gates # including initial inputs and outputs of Toffoli gates # eg: ["X0", "X1", "X0"], the first (or the second) "X0" is a zero state # we have a rule to select zero states # for a linear B with rank(B)=dim, there are size(B)-dim zero states # select k=dim nonzero states from front(0) to back(size-1) such that rank is dim # the remainder size(B)-dim states are zero states(eg: the second "X0") def check_two_states_qubits(states: list, n: int, dim: int): states_n = len(states) # r states, rank is r nonzero_states = [] nonzero_qubits = [] for i in range(states_n): rank1 = linears_to_matrix(L=nonzero_states, n=n, cols=dim).rank() # rank = r if rank1 == dim: break state = states[i] nonzero_states.append(state) rank2 = linears_to_matrix(L=nonzero_states, n=n, cols=dim).rank() # rank does not increase. Do not select this state if rank2 == rank1: nonzero_states.pop() else: nonzero_qubits.append(i) zero_qubits = list(set([i for i in range(states_n)]) - set(nonzero_qubits)) return nonzero_qubits, zero_qubits # select some S[i-1] to S[i] # initial_states : necessary states # candidate_states : selectable states # n : number of initial variables # dim : number of variables # target_n : number of target state # select some states from candidate_states and add them to initial_states # such that rank(selected_states) = dim, full column rank def select_step(initial_states: list, candidate_states: list, n:int, dim: int, target_n: int): states_n = len(candidate_states) selected_states = initial_states[:] # print(f"initial size: {len(selected_states)}") # idx of nonzeros states(can add rank) nonzero_states_idxs = [] # 1. select nonzeros states such that rank = dim for i in range(states_n): rank1 = linears_to_matrix(L=selected_states, n=n, cols=dim).rank() # rank = dim if rank1 == dim: break state = candidate_states[i] selected_states.append(state) rank2 = linears_to_matrix(L=selected_states, n=n, cols=dim).rank() # rank does not increas. Do not select this state if rank2 == rank1: selected_states.pop() else: nonzero_states_idxs.append(i) # 2. select (target_n-dim) zero states zero_states_idxs = list(set([i for i in range(states_n)]) - set(nonzero_states_idxs)) # fill size to target_n zero_states = [candidate_states[i] for i in zero_states_idxs[:target_n-len(selected_states)]] selected_states = selected_states + zero_states # print(f"selected_states: \n{selected_states}") # print(f"final size: {len(selected_states)}") return selected_states # transform S_pre to S # (1) size(S_pre) = size(S) # (2) rank(S_pre) = rank(S) = dim(number of columns) def get_state_transform_matrix(S_pre, S, n, dim): rows = len(S_pre) # Si_pre -> A, Si -> B, find an invertible matrix MT # such that MT * A = B A = linears_to_matrix(L=S_pre, n=n, cols=dim) B = linears_to_matrix(L=S, n=n, cols=dim) IMA = A.extended_echelon_form(transformation=True) IMB = B.extended_echelon_form(transformation=True) # the nxn matrices in right MA = [[0 for j in range(rows)] for i in range(rows)] MB = [[0 for j in range(rows)] for i in range(rows)] for i in range(rows): for j in range(rows): MA[i][j] = IMA[i][dim+j] MB[i][j] = IMB[i][dim+j] MT = matGF2(MB).inverse() * matGF2(MA) return MT # the last AND(Toffoli) layer to outputs layer # CNOT-s -> Toffoli-s -> CNOT-outputs # n : number of inputs x # m : number of outputs y # cur_dim : current number of variables # cur_states : current states # Bs : CNOT-s linear(affine) expressions # By : outputs Y linear(affine) expressions def output_part(n: int, m: int, cur_dim: int, cur_states: list, Bs: list, By: list): dim = cur_dim states = cur_states[:] width = len(states) qubits = [i for i in range(width)] # need to store CNOT_layers_qubits = [] Toffoli_layers_qubits = [] state_transform_matrices = [] # AND-layer-s ns = len(Bs) // 2 a = 2*ns - linears_to_matrix(Bs, n=n, cols=dim).rank() nonzero_qubits, zero_qubits = check_two_states_qubits(states=states, n=n, dim=dim) zero_states = [states[i] for i in zero_qubits] nonzero_states = [states[i] for i in nonzero_qubits] zero_states_n = len(zero_states) # additionally consider outputs qubits(at least m newly allocated) allocate_qubits_n = m clean_for_Toffoli_outputs = 0 # (1) last outputs of Toffoli layer less than m if ns <= m: # use zero states for inputs of Toffoli gates(zero_states_n - a < 0) # allocate (a - zero_states_n) > 0, or no need to allocate allocate_qubits_n += max(a - zero_states_n, 0) # (2) ns > m, some outputs of Toffoli can store in old qubits else: # CNOT sub-circuit needs to allocate qubits(obviously Toffoli layer also needs) if a - zero_states_n > 0: allocate_qubits_n += (a - zero_states_n) + (ns - m) # CNOT sub-circuit does not need to allocate qubits else: clean_for_Toffoli_outputs = min(zero_states_n - a, ns - m) # Toffoli layer needs to allocate qubits allocate_qubits_n += (ns - m) - clean_for_Toffoli_outputs # allocate qubits (last m qubits storing the outputs Y) print(f"##### allocate qubits: {allocate_qubits_n} #####") zero_states += ["" for i in range(allocate_qubits_n)] zero_qubits += [width+i for i in range(allocate_qubits_n)] states += ["" for i in range(allocate_qubits_n)] qubits += [width+i for i in range(allocate_qubits_n)] width += allocate_qubits_n # CNOT (1)nonzero (2) zero(inputs) (3) zero(outputs) CNOT_n = dim + a + clean_for_Toffoli_outputs # qubits of CNOT and Toffoli layer CNOT_qubits = nonzero_qubits[:] + zero_qubits[:a+clean_for_Toffoli_outputs] Toffoli_qubits = zero_qubits[a:a+(ns-m)] + zero_qubits[-m:] # the inputs and outputs of the CNOT sub-circuit Ss_pre_states = nonzero_states[:] + zero_states[:a+clean_for_Toffoli_outputs] Ss_states = select_step(initial_states=Bs, candidate_states=Ss_pre_states , n=n, dim=dim, target_n=CNOT_n-clean_for_Toffoli_outputs) # clean up Toffoli outputs Ss_states += ["" for i in range(clean_for_Toffoli_outputs)] # find a CNOT circuit(invertible matrix MT) that maps S'[s-1] to S'[s] MT = get_state_transform_matrix(S_pre=Ss_pre_states, S=Ss_states, n=n, dim=dim) # update states of CNOT and Toffoli layer for j in range(CNOT_n): cj = CNOT_qubits[j] states[cj] = Ss_states[j] # ni Toffoli variables(use "M") for j in range(ns): tj = Toffoli_qubits[j] states[tj] = f"M{cur_dim-n+j}" dim += ns # need to store three parts CNOT_layers_qubits.append(CNOT_qubits) Toffoli_layers_qubits.append(Toffoli_qubits) state_transform_matrices.append(MT) # Generate the outputs Y (need store in the last allocated qubits) outputs_qubits = qubits[-m:] ny = len(By) // 2 ay = 2*ny - linears_to_matrix(By, n=n, cols=dim).rank() nonzero_qubits, zero_qubits = check_two_states_qubits(states=states, n=n, dim=dim) zero_states = [states[i] for i in zero_qubits] nonzero_states = [states[i] for i in nonzero_qubits] CNOT_n = dim + ay # before m qubits are outputs qubits CNOT_qubits = outputs_qubits[:] for q in nonzero_qubits: if q not in CNOT_qubits: CNOT_qubits.append(q) CNOT_qubits += zero_qubits[:ay] Y_pre_states = [states[i] for i in CNOT_qubits] Y_states = select_step(initial_states=By, candidate_states=Y_pre_states , n=n, dim=dim, target_n=CNOT_n) MT_Y = get_state_transform_matrix(S_pre=Y_pre_states, S=Y_states, n=n, dim=dim) CNOT_layers_qubits.append(CNOT_qubits) state_transform_matrices.append(MT_Y) output_part_returns = [dim, width, CNOT_layers_qubits, Toffoli_layers_qubits, state_transform_matrices] return output_part_returns # Algorithm : classical to quantum # n : number of inputs x # m : number of outputs y # s : AND-depth # C : classical circuit (uses gates[NOT, XOR, AND]) def classical_to_quantum(n: int, m: int, s: int, C: list): print(f"##### classical to quantum #####") print(f"##### AND-depth: {s} #####") # 1+s+1 linear expressions B = extract_linear(n=n, m=m, C=C) # width: all qubits width = n print(f"##### allocate qubits: {n} #####") # linear(affine) states use string, eg: "X0+M1" states = [f"X{i}" for i in range(n)] # index qubits = [i for i in range(n)] # number of variable(columns of matrix, n+n1+n2+...ns) dim = n # need to store state_transform_matrices = [] CNOT_layers_qubits = [] Toffoli_layers_qubits = [] # 2. each AND layer to Toffoli(QAND) layer # note that the index starts from 1(not 0) is the inputs of AND-layer-1 # idx 0 is the initial n inputs X for i in range(1, s): # print(f"##### AND-Layer-{i} #####") # ni: number of AND gates in this layer(2ni inputs) ni = len(B[i]) // 2 # ai: number of ancilla qubits in the CNOT sub-circuit ai = 2*ni - linears_to_matrix(B[i], n=n, cols=dim).rank() nonzero_qubits, zero_qubits = check_two_states_qubits(states=states, n=n, dim=dim) zero_states = [states[j] for j in zero_qubits] nonzero_states = [states[j] for j in nonzero_qubits] zero_states_n = len(zero_states) # firstly use zero states for inputs of Toffoli gates # if still zero states left, can be cleaned to "0" for outputs of Toffoli gates clean_for_Toffoli_outputs = min(zero_states_n - ai, ni) # no zero states can be cleaned to 0 if clean_for_Toffoli_outputs < 0: clean_for_Toffoli_outputs = 0 allocate_qubits_n = ni + ai - zero_states_n # allocate qubits, then size(zero_states) >= ni+ai if allocate_qubits_n > 0: print(f"##### allocate qubits: {allocate_qubits_n} #####") zero_states += ["" for j in range(allocate_qubits_n)] zero_qubits += [width+j for j in range(allocate_qubits_n)] states += ["" for j in range(allocate_qubits_n)] qubits += [width+j for j in range(allocate_qubits_n)] width += allocate_qubits_n # CNOT (1)nonzero (2) zero(inputs) (3) zero(outputs) CNOT_n = dim + ai + clean_for_Toffoli_outputs # qubits for CNOT and Toffoli layer CNOT_qubits = nonzero_qubits[:] + zero_qubits[:ai+clean_for_Toffoli_outputs] Toffoli_qubits = zero_qubits[ai:ai+ni] # the inputs and outputs of CNOT sub-circuit Si_pre_states = nonzero_states[:] + zero_states[:ai+clean_for_Toffoli_outputs] Si_states = select_step(initial_states=B[i], candidate_states=Si_pre_states , n=n, dim=dim, target_n=CNOT_n-clean_for_Toffoli_outputs) # clean up Toffoli outputs Si_states += ["" for j in range(clean_for_Toffoli_outputs)] # find a CNOT circuit (invertible matrix MT) that maps S'[i-1] to S'[i] MT = get_state_transform_matrix(S_pre=Si_pre_states, S=Si_states, n=n, dim=dim) # update states of the CNOT and Toffoli layer for j in range(CNOT_n): cj = CNOT_qubits[j] states[cj] = Si_states[j] # ni Toffoli variables(use "M") for j in range(ni): tj = Toffoli_qubits[j] states[tj] = f"M{dim-n+j}" dim += ni # need to store three parts CNOT_layers_qubits.append(CNOT_qubits) Toffoli_layers_qubits.append(Toffoli_qubits) state_transform_matrices.append(MT) # output_part() return results output_part_returns = output_part(n=n, m=m, cur_dim=dim, cur_states=states, Bs=B[s], By=B[s+1]) dim = output_part_returns[0] width = output_part_returns[1] CNOT_layers_qubits += output_part_returns[2] Toffoli_layers_qubits += output_part_returns[3] state_transform_matrices += output_part_returns[4] print(f"##### width: {width} #####") qc_info = quantum_circuit_info(n, m, s, width, CNOT_layers_qubits , Toffoli_layers_qubits, state_transform_matrices) return qc_info # verify quantum circuit by CNOT sub-circuit, Toffoli layer def verify_quantum_circuit(qc_info: quantum_circuit_info, outputs_ANF: list): n = qc_info.n m = qc_info.m s = qc_info.s width = qc_info.width CNOT_layers_qubits = qc_info.CNOT_layers_qubits Toffoli_layers_qubits = qc_info.Toffoli_layers_qubits state_transform_matrices = qc_info.state_transform_matrices # variable X over Boolean Polynomial Ring BV = BooleanPolynomialRing(names=[f"X{i}" for i in range(n)]) # states N = [BV(f"X{i}") for i in range(n)] + [0 for i in range(width-n)] # CNOT sub-circuit -> Toffoli layer->... for i in range(s): CNOT_qubits = CNOT_layers_qubits[i] Toffoli_qubits = Toffoli_layers_qubits[i] MT = state_transform_matrices[i] CNOT_inputs = [N[j] for j in CNOT_qubits] CNOT_outputs = MT * vector(CNOT_inputs) # CNOT sub-circuit for j in range(len(CNOT_qubits)): cj = CNOT_qubits[j] N[cj] = CNOT_outputs[j] # Toffoli layer for j in range(len(Toffoli_qubits)): tj = Toffoli_qubits[j] N[tj] += CNOT_outputs[2*j] * CNOT_outputs[2*j+1] # outputs(CNOT) layer CNOT_qubits = CNOT_layers_qubits[-1] MT_Y = state_transform_matrices[-1] CNOT_inputs = [N[j] for j in CNOT_qubits] CNOT_outputs = MT_Y * vector(CNOT_inputs) for j in range(len(CNOT_qubits)): cj = CNOT_qubits[j] N[cj] = CNOT_outputs[j] print(f"##### verify T-depth-{s} quantum circuit #####") print(f"##### width: {width} #####") print(f"##### Y store in qubits: {CNOT_qubits[:m]} #####") Y = CNOT_outputs[:m] for i in range(8): print(Y[i] + outputs_ANF[i], end=" ") print("\n#############################################") n=8 # variable X over Boolean Polynomial Ring BV = BooleanPolynomialRing(names=[f"X{i}" for i in range(n)]) AES_Sbox_ANF = get_AES_Sbox_ANF(X=[BV(f"X{i}") for i in range(n)]) ###################################################################################### # classical circuit("X" -> inputs, "Y" -> "outputs") AES_Sbox_ANDDepth3_42AND = ["X0=X0+1", "L0=X0+X3", "L1=X0+X6", "L2=X0+X5", "L3=L0+L2", "L4=L1+L3", "L5=X4+L4", "L6=X5+L5", "L7=X7+L6", "L8=X1+X2", "L9=X7+L8", "L10=X3+L9", "L11=L4+L10", "L12=L2+L11", "L13=L1+L12", "L14=L7+L9", "L15=X1+L0", "L16=L5+L15", "L17=X7+L16", "L18=L9+L17", "L19=L14+L16", "M0=L7*L11", "M1=X7*L10", "M2=L9*L12", "M3=L13*L17", "M4=L4*L6", "M5=L0*L16", "M6=L1*L18", "M7=L2*L14", "M8=L3*L19", "L20=X2+M0", "L21=L1+M2", "L22=L15+M4", "L23=L22+M6", "L24=L20+L21", "L25=L23+L24", "L26=M1+M3", "L27=L23+L26", "L28=L25+L27", "L29=L18+M5", "L30=X0+M7", "L31=L21+M8", "L32=L29+M6", "L33=L31+L32", "L34=L25+L33", "L35=L30+M3", "L36=L32+L35", "L37=L27+L36", "L38=L34+L37", "L39=L33+L36", "M9=L33*L34", "M10=L36*L37", "M11=L38*L39", "M12=L11*L33", "M13=L10*L36", "M14=L12*L34", "M15=L13*L37", "M16=L4*L39", "M17=L0*L27", "M18=L1*L38", "M19=L2*L25", "M20=L3*L28", "M21=L7*L33", "M22=X7*L36", "M23=L9*L34", "M24=L17*L37", "M25=L6*L39", "M26=L16*L27", "M27=L18*L38", "M28=L14*L25", "M29=L19*L28", "L40=M10+M11", "L41=L28+L40", "L42=L27+M9", "L43=L42+M10", "L44=L41+L43", "L45=M19+M20", "L46=M17+M20", "L47=L46+M18", "L48=L47+M14", "L49=L45+M15", "L50=L47+L49", "L51=L48+L50", "L52=L46+M16", "L53=L52+M12", "L54=L45+M13", "L55=L52+L54", "L56=L53+L55", "L57=M28+M29", "L58=M26+M29", "L59=L58+M27", "L60=L59+M23", "L61=L57+M24", "L62=L59+L61", "L63=L60+L62", "L64=L58+M25", "L65=L64+M21", "L66=L57+M22", "L67=L64+L66", "L68=L65+L67", "M30=L41*L53", "M31=L44*L55", "M32=L43*L56", "M33=L41*L48", "M34=L44*L50", "M35=L43*L51", "M36=L41*L65", "M37=L44*L67", "M38=L43*L68", "M39=L41*L60", "M40=L44*L62", "M41=L43*L63", "L69=M30+M32", "L70=L69+M39", "L71=M37+M38", "L72=L71+M35", "L73=M36+M38", "L74=M40+M41", "L75=L72+L74", "Y0=L70+M41", "Y1=L69+L73", "Y2=L72+M34", "Y3=L73+Y0", "Y7=L75+M33", "L76=L71+Y3", "L77=L76+Y7", "L78=M32+Y2", "L79=L78+Y0", "Y4=L74+L76", "Y5=L79+M31", "Y6=L77+Y1", "Y1=Y1+1", "Y2=Y2+1", "Y6=Y6+1", "Y7=Y7+1"] # Three instances: # 1) The AND-depth-4 and AND-count-34 classical circuit for the AES S-box (Boyar and Peralta's S-box) # 2) The AND-depth-3 and AND-count-42 classical circuit for the AES S-box introduced in Section 5.2 # 3) The AND-depth-3 and AND-count-78 classical circuit for the AES S-box introduced in ASIACRYPT 2022 # AND_numbers=[9,3,4,18] # AND_numbers=[9,21,12] qc_info2 = classical_to_quantum(n=8, m=8, s=3, C=AES_Sbox_ANDDepth3_42AND) # AND_numbers=[9,33,36] # print and save quantum circuit information qc_info2.print_Info(save_file_name="AES_Sbox_T3_42Toffoli_info.txt") verify_quantum_circuit(qc_info=qc_info2, outputs_ANF=AES_Sbox_ANF) 运行这段代码
最新发布
11-28
<think> 我们有一段SageMath代码,目的是将AES S-box的经典电路(由与门、异或门、非门组成)转换为量子电路(使用CNOT门和Toffoli门)。代码中实现了名为`classical_to_quantum`的函数,该函数接受输入数量n、输出数量m、与门深度s和经典电路C(字符串列表),返回一个量子电路信息对象(`quantum_circuit_info`)。 代码中提供了AES S-box的一个经典电路实现(`AES_Sbox_ANDDepth3_42AND`),该电路与门深度为3,包含42个与门。然后,调用`classical_to_quantum`函数将其转换为量子电路,并验证量子电路的正确性。 然而,由于代码较长且复杂,我们无法在本地运行SageMath环境。但是,我们可以分析代码并解释其工作原理。 主要步骤: 1. 定义`quantum_circuit_info`类,用于存储量子电路的信息,包括输入数n、输出数m、T深度s、量子比特数width、CNOT层的量子比特、Toffoli层的量子比特以及状态转换矩阵(CNOT子电路的线性变换矩阵)。 2. 辅助函数: - `matGF2`:将列表转换为GF(2)上的矩阵。 - `linears_to_matrix`:将线性表达式(字符串形式)转换为矩阵表示。 - `get_AES_Sbox_ANF`:返回AES S-box的代数正规形式(ANF)表达式。 3. `extract_linear`函数:从经典电路中提取线性表达式。该函数分析经典电路,计算每个变量的与门深度,并将所有变量用输入变量(X)和与门输出(M)的线性组合表示。 4. `check_two_states_qubits`函数:将状态(线性表达式)分为非零状态和零状态(可以归零的状态),用于选择CNOT子电路的输入量子比特。 5. `select_step`函数:从候选状态中选择一些状态,使得所选状态的秩等于目标维度,并满足目标数量。 6. `get_state_transform_matrix`函数:计算两个状态集合之间的线性变换矩阵(可逆矩阵),该矩阵表示一个CNOT电路。 7. `output_part`函数:处理最后一个与门层到输出层的转换。 8. `classical_to_quantum`函数:主函数,执行经典到量子电路的转换。它首先提取线性表达式,然后逐层(与门层)处理,每一层包括一个CNOT子电路和一个Toffoli层。最后处理输出部分。 9. `verify_quantum_circuit`函数:验证量子电路是否正确计算了AES S-box。 10. 最后,代码对AES S-box的一个具体电路(42个与门,深度3)进行转换和验证。 由于代码较长,我们只关注主流程。现在,我们尝试解释整个转换过程。 转换过程- 经典电路由一系列指令组成,如“X0=X0+1”(非门)、“L0=X0+X3”(异或门)、“M0=L7*L11”(与门)。 - 首先,`extract_linear`函数解析电路,记录每个变量的与门深度,并将所有变量表示为输入变量(X)和与门输出(M)的线性组合。 - 转换算法将每个与门层(深度为s)转换为量子电路中的一个Toffoli层,并在每个Toffoli层之前和之后插入CNOT子电路(线性变换)。 - 对于每个与门层,算法计算所需的辅助量子比特数量,并分配新的量子比特(如果需要)。然后,构建一个CNOT子电路,将当前状态转换为Toffoli门所需的输入状态。接着,应用Toffoli门(在量子电路中,Toffoli门需要三个量子比特:两个控制位和一个目标位,但这里目标位可能是新分配的量子比特或清零的量子比特)。 - 最后,输出层(Y)同样通过一个CNOT子电路实现。 验证过程- 通过模拟量子电路(在GF(2)上进行线性代数和双线性乘法)计算输出,并与AES S-box的ANF表达式比较。 代码运行: - 代码最后部分调用`classical_to_quantum`函数转换特定电路,并验证。 注意:由于代码依赖SageMath环境,且包含大量矩阵运算,因此在实际运行中需要SageMath。 由于无法运行,我们只能根据代码逻辑解释。如果您有SageMath环境,可以直接运行。 相关问题: 1. 如何在没有SageMath的情况下验证结果? 2. 量子电路的宽度(量子比特数)是如何确定的? 3. 状态转换矩阵(CNOT子电路)是如何生成的? 4. 该算法是否适用于任意经典电路(前提是电路由与门、异或门、非门组成)? </think> 这段代码实现了一个将AES S-box的经典电路转换为量子电路的算法,主要使用SageMath环境。以下是对代码的详细分析和解释: ### 代码功能概述 1. **目标**:将AES S-box的经典电路(由AND、XOR、NOT门组成)转换为量子电路(使用CNOT和Toffoli门) 2. **核心算法**:`classical_to_quantum()`函数实现了从经典电路到量子电路的转换 3. **关键特性**: - 保持相同的AND深度(T深度) - 优化量子比特数量(宽度) - 生成可验证的量子电路结构 ### 核心类:`quantum_circuit_info` ```python class quantum_circuit_info(): def __init__(self, n: int, m: int, s: int, width: int, CNOT_layers_qubits: list, Toffoli_layers_qubits: list, state_transform_matrices: list): self.n = n # 输入数量 self.m = m # 输出数量 self.s = s # T深度 self.width = width # 量子比特数 self.CNOT_layers_qubits = CNOT_layers_qubits # CNOT层量子比特 self.Toffoli_layers_qubits = Toffoli_layers_qubits # Toffoli层量子比特 self.state_transform_matrices = state_transform_matrices # 状态转换矩阵 ``` ### 关键函数解析 #### 1. `extract_linear()` - 提取线性表达式 ```python def extract_linear(n: int, m: int, C: list): # 分析电路中的AND门深度 # 构建布尔多项式环 # 将电路中所有变量表示为输入(X)和AND输出(M)的线性组合 # 返回分层线性表达式 ``` #### 2. `classical_to_quantum()` - 核心转换算法 ```python def classical_to_quantum(n: int, m: int, s: int, C: list): # 1. 提取线性表达式 B = extract_linear(n, m, C) # 2. 初始化量子比特状态 states = [f"X{i}" for i in range(n)] width = n # 3. 处理每个AND层 for i in range(1, s): # 计算所需辅助比特 # 选择量子比特用于CNOT和Toffoli # 计算状态转换矩阵 # 更新量子比特状态 # 4. 处理输出层 output_part_returns = output_part(...) # 5. 返回量子电路信息 return quantum_circuit_info(...) ``` #### 3. `output_part()` - 输出层处理 ```python def output_part(n: int, m: int, cur_dim: int, cur_states: list, Bs: list, By: list): # 分配输出量子比特 # 构建最终CNOT子电路 # 计算输出转换矩阵 # 返回完整电路信息 ``` #### 4. `verify_quantum_circuit()` - 电路验证 ```python def verify_quantum_circuit(qc_info, outputs_ANF): # 模拟量子电路操作 # 将输出与预计算的ANF比较 # 验证正确性 ``` ### AES S-box实现示例 ```python AES_Sbox_ANDDepth3_42AND = [ "X0=X0+1", "L0=X0+X3", ... # 42个AND门的深度3电路 ] ``` ### 代码执行流程 1. 定义AES S-box的ANF(代数正规形式) 2. 加载深度3的经典电路实现 3. 调用`classical_to_quantum`进行转换 4. 打印并保存量子电路信息 5. 验证量子电路正确性 ### 量子电路特性 对于给出的AES S-box实现(深度3,42个AND门): - **T深度**:3(与经典电路AND深度相同) - **量子比特数**:根据算法动态分配 - **电路结构**: - 交替的CNOT子电路和Toffoli层 - 最终输出转换层 ### 运行说明 1. **依赖**:需要SageMath环境 2. **执行**:直接运行脚本 3. **输出**: - 量子电路信息文件(`AES_Sbox_T3_42Toffoli_info.txt`) - 验证结果输出到控制台 ### 代码优化点 1. 使用矩阵运算高效处理线性变换 2. 动态量子比特分配策略 3. 分层处理保持电路深度优化 4. 完整的验证机制保证正确性 ### 潜在改进方向 1. 添加更多电路实现选项 2. 优化量子比特重用策略 3. 生成可执行的量子电路代码(如QASM) 4. 添加可视化输出功能 ### 总结 这段代码实现了从经典AES S-box电路到量子电路的转换算法,核心贡献在于: 1. 保持相同的T深度(AND深度) 2. 优化量子比特数量 3. 提供完整的验证机制 4. 模块化设计便于扩展 以下是一个简化的示例,展示如何在自己的环境中使用类似功能(Python实现非SageMath版本): ```python # 简化的量子电路转换示例(概念演示) class QuantumCircuitConverter: def __init__(self, n, m): self.n = n # 输入比特数 self.m = m # 输出比特数 self.qubits = n # 当前量子比特数 self.operations = [] def add_cnot_layer(self, controls, targets): """添加CNOT层""" self.operations.append(('CNOT', controls, targets)) def add_toffoli_layer(self, controls, targets): """添加Toffoli层""" self.operations.append(('Toffoli', controls, targets)) def allocate_qubits(self, num): """分配新量子比特""" new_qubits = list(range(self.qubits, self.qubits + num)) self.qubits += num return new_qubits def convert_classical_circuit(self, classical_circuit): """转换经典电路(简化示例)""" # 步骤1: 初始设置 current_states = [f'X{i}' for i in range(self.n)] # 步骤2: 处理每个逻辑门 for gate in classical_circuit: if 'AND' in gate: # 分配新量子比特用于Toffoli输出 new_qubit = self.allocate_qubits(1)[0] # 添加Toffoli门 self.add_toffoli_layer(controls=[...], targets=[new_qubit]) # 更新状态 current_states.append(f'M{len(current_states)-self.n}') # 步骤3: 输出处理 output_qubits = self.allocate_qubits(self.m) self.add_cnot_layer(controls=[...], targets=output_qubits) return { 'width': self.qubits, 'operations': self.operations, 'output_qubits': output_qubits } # 示例用法 converter = QuantumCircuitConverter(n=8, m=8) quantum_circuit = converter.convert_classical_circuit(AES_Sbox_circuit) print(f"量子比特数: {quantum_circuit['width']}") print(f"门操作数: {len(quantum_circuit['operations'])}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值