Source code for skqulacs.circuit.pre_defined

from functools import reduce
from math import factorial
from typing import List, Optional

import numpy as np
from numpy.random import Generator, default_rng
from numpy.typing import NDArray
from qulacs.gate import CNOT, CZ, DenseMatrix

from .circuit import LearningCircuit


[docs]def create_qcl_ansatz( n_qubit: int, c_depth: int, time_step: float = 0.5, seed: Optional[int] = 0 ) -> LearningCircuit: """Create a circuit used in this page: https://dojo.qulacs.org/ja/latest/notebooks/5.2_Quantum_Circuit_Learning.html Args: n_qubit: number of qubits c_depth: circuit depth time_step: the evolution time used for the hamiltonian dynamics seed: seed for random numbers. used for determining the interaction strength of the hamiltonian simulation Examples: >>> n_qubit = 4 >>> circuit = create_qcl_ansatz(n_qubit, 3, 0.5) >>> qnn = QNNRegressor(circuit) >>> qnn.fit(x_train, y_train) """ def preprocess_x(x: NDArray[np.float_], index: int) -> float: xa = x[index % len(x)] clamped: float = min(1, max(-1, xa)) return clamped circuit = LearningCircuit(n_qubit) for i in range(n_qubit): # Capture copy of i by `i=i`. # Without this, i in lambda is a reference to the i, so the lambda always # recognize i as n_qubit - 1. circuit.add_input_RY_gate(i, lambda x, i=i: np.arcsin(preprocess_x(x, i))) circuit.add_input_RZ_gate( i, lambda x, i=i: np.arccos(preprocess_x(x, i) * preprocess_x(x, i)) ) rng = default_rng(seed) time_evol_gate = _create_time_evol_gate(n_qubit, time_step) for _ in range(c_depth): circuit.add_gate(time_evol_gate) for i in range(n_qubit): angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(i, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RZ_gate(i, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(i, angle) return circuit
def _create_time_evol_gate( n_qubit, time_step=0.77, rng: Optional[Generator] = None, seed: Optional[int] = 0 ): """create a hamiltonian dynamics with transverse field ising model with random interaction and random magnetic field Args: n_qubit: number of qubits time_step: evolution time rng: random number generator seed: seed for random number Return: qulacs' gate object """ if rng is None: rng = default_rng(seed) ham = _make_hamiltonian(n_qubit, rng) # Create time evolution operator by diagonalization. # H*P = P*D <-> H = P*D*P^dagger diag, eigen_vecs = np.linalg.eigh(ham) time_evol_op = np.dot( np.dot(eigen_vecs, np.diag(np.exp(-1j * time_step * diag))), eigen_vecs.T.conj() ) # e^-iHT # Convert to a qulacs gate time_evol_gate = DenseMatrix([i for i in range(n_qubit)], time_evol_op) return time_evol_gate def _make_hamiltonian( n_qubit, rng: Optional[Generator] = None, seed: Optional[int] = 0 ): if rng is None: rng = default_rng(seed) X_mat = np.array([[0, 1], [1, 0]]) Z_mat = np.array([[1, 0], [0, -1]]) ham = np.zeros((2**n_qubit, 2**n_qubit), dtype=complex) for i in range(n_qubit): Jx = rng.uniform(-1.0, 1.0) ham += Jx * _make_fullgate([[i, X_mat]], n_qubit) for j in range(i + 1, n_qubit): J_ij = rng.uniform(-1.0, 1.0) ham += J_ij * _make_fullgate([[i, Z_mat], [j, Z_mat]], n_qubit) return ham def _make_fullgate(list_SiteAndOperator, n_qubit): """ Receive `list_SiteAndOperator = [ [i_0, O_0], [i_1, O_1], ...]` and insert identity to qubits which is not present in the list to create (2**n_qubit, 2**n_qubit) matrix I(0) * ... * O_0(i_0) * ... * O_1(i_1) ... """ I_mat = np.eye(2, dtype=complex) list_Site = [SiteAndOperator[0] for SiteAndOperator in list_SiteAndOperator] list_SingleGates = [] cnt = 0 for i in range(n_qubit): if i in list_Site: list_SingleGates.append(list_SiteAndOperator[cnt][1]) cnt += 1 else: list_SingleGates.append(I_mat) return reduce(np.kron, list_SingleGates)
[docs]def create_farhi_neven_ansatz( n_qubit: int, c_depth: int, seed: Optional[int] = 0 ) -> LearningCircuit: """create circuit proposed in https://arxiv.org/abs/1802.06002. Args: n_qubits: number of qubits c_depth: depth of the circuit seed: random seed determining the shuffling of the qubits between layers """ def preprocess_x(x: NDArray[np.float_], index: int) -> float: xa = x[index % len(x)] clamped = min(1, max(-1, xa)) return clamped circuit = LearningCircuit(n_qubit) for i in range(n_qubit): circuit.add_input_RY_gate(i, lambda x, i=i: np.arcsin(preprocess_x(x, i))) circuit.add_input_RZ_gate( i, lambda x, i=i: np.arccos(preprocess_x(x, i) * preprocess_x(x, i)) ) zyu = list(range(n_qubit)) rng = default_rng(seed) for _ in range(c_depth): rng.shuffle(zyu) for i in range(0, n_qubit - 1, 2): angle_x = 2.0 * np.pi * rng.random() angle_y = 2.0 * np.pi * rng.random() circuit.add_CNOT_gate(zyu[i + 1], zyu[i]) circuit.add_parametric_RX_gate(zyu[i], angle_x) circuit.add_parametric_RY_gate(zyu[i], angle_y) circuit.add_CNOT_gate(zyu[i + 1], zyu[i]) angle_x = 2.0 * np.pi * rng.random() angle_y = 2.0 * np.pi * rng.random() circuit.add_parametric_RY_gate(zyu[i], -angle_y) circuit.add_parametric_RX_gate(zyu[i], -angle_x) return circuit
[docs]def create_farhi_neven_watle_ansatz( n_qubit: int, c_depth: int, seed: Optional[int] = 0 ) -> LearningCircuit: """create modified version of circuit proposed in https://arxiv.org/abs/1802.06002. made by WA_TLE. Args: n_qubits: number of qubits c_depth: depth of the circuit seed: random seed determining the shuffling of the qubits between layers """ xkeisuu = np.zeros([25, 25, 25]) nCr = np.zeros([25, 25]) for i in range(25): for j in range(i + 1): nCr[i][j] = factorial(i) / factorial(j) / factorial(i - j) for i in range(25): for j in range(i): if j == 0: xkeisuu[i][0][i] = 1 else: for k in range(i - j, i + 1): xkeisuu[i][j][k] = xkeisuu[i][j - 1][k] + nCr[i][j] * nCr[j][ i - k ] * ((-1) ** (i + j + k)) def preprocess_x(x: NDArray[np.float_], index: int): dex = index % len(x) qubits_per_bit = ((n_qubit - dex) - 1) // len(x) + 1 xa = (min(1, max(-1, x[dex])) + 1) / 2 sban = index // len(x) xb = 0 if qubits_per_bit < 25: for i in range(qubits_per_bit): xb += xkeisuu[qubits_per_bit][sban][qubits_per_bit - i] xb *= xa else: # Overflow `double` type of Combination(n, r). # There are few cases to use the same 25 bits. xb = xa if xb < 0 or 1 < xb: raise RuntimeError("bug") return xb * 2 - 1 circuit = LearningCircuit(n_qubit) for i in range(n_qubit): circuit.add_input_RY_gate(i, lambda x, i=i: np.arcsin(preprocess_x(x, i))) circuit.add_input_RZ_gate( i, lambda x, i=i: np.arccos(preprocess_x(x, i) * preprocess_x(x, i)) ) zyu = list(range(n_qubit)) rng = default_rng(seed) for _ in range(c_depth): rng.shuffle(zyu) for i in range(0, n_qubit - 1, 2): angle_x = 2.0 * np.pi * rng.random() angle_y = 2.0 * np.pi * rng.random() circuit.add_CNOT_gate(zyu[i + 1], zyu[i]) circuit.add_parametric_RX_gate(zyu[i], angle_x) circuit.add_parametric_RY_gate(zyu[i], angle_y) circuit.add_CNOT_gate(zyu[i + 1], zyu[i]) angle_x = 2.0 * np.pi * rng.random() angle_y = 2.0 * np.pi * rng.random() circuit.add_parametric_RY_gate(zyu[i], -angle_y) circuit.add_parametric_RX_gate(zyu[i], -angle_x) return circuit
[docs]def create_ibm_embedding_circuit(n_qubit: int) -> LearningCircuit: """create circuit proposed in https://arxiv.org/abs/1802.06002. Args: n_qubits: number of qubits """ def preprocess_x(x: NDArray[np.float_], index: int) -> float: xa: float = x[index % len(x)] return xa circuit = LearningCircuit(n_qubit) for i in range(n_qubit): circuit.add_H_gate(i) for i in range(n_qubit): j = (i + 1) % n_qubit circuit.add_input_RZ_gate(i, lambda x, i=i: preprocess_x(x, i)) circuit.add_CNOT_gate(i, j) circuit.add_input_RZ_gate( j, lambda x, i=i: ( (np.pi - preprocess_x(x, i)) * (np.pi - preprocess_x(x, j)) ), ) circuit.add_CNOT_gate(i, j) for i in range(n_qubit): circuit.add_H_gate(i) for i in range(n_qubit): j = (i + 1) % n_qubit circuit.add_input_RZ_gate(i, lambda x, i=i: preprocess_x(x, i)) circuit.add_CNOT_gate(i, j) circuit.add_input_RZ_gate( j, lambda x, i=i: ( (np.pi - preprocess_x(x, i)) * (np.pi - preprocess_x(x, j)) ), ) circuit.add_CNOT_gate(i, j) return circuit
[docs]def create_shirai_ansatz( n_qubit: int, c_depth: int = 5, seed: Optional[int] = 0 ) -> LearningCircuit: """create circuit proposed in http://arxiv.org/abs/2111.02951. Args: n_qubit: number of qubits c_depth: circuit depth as defined in http://arxiv.org/abs/2111.02951 seed: random seed for initial parameter values """ def preprocess_x(x: NDArray[np.float_], index: int) -> float: xa: float = x[index % len(x)] return xa rng = default_rng(seed) circuit = LearningCircuit(n_qubit) for _ in range(c_depth): # input embedding layer for i in range(n_qubit): circuit.add_input_RZ_gate(i, lambda x, i=i: np.arcsin(preprocess_x(x, i))) for j in range(i): circuit.add_CNOT_gate(j, i) circuit.add_input_RZ_gate( i, lambda x, i=i: -np.arcsin(preprocess_x(x, i) * preprocess_x(x, j)) / 2, ) circuit.add_CNOT_gate(j, i) circuit.add_input_RZ_gate( i, lambda x, i=i: np.arcsin(preprocess_x(x, i) * preprocess_x(x, j)) / 2, ) # trainable layer for i in range(0, n_qubit): j = (i + 1) % n_qubit angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(i, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RZ_gate(i, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(i, angle) circuit.add_CNOT_gate(i, j) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(i, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RZ_gate(i, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(i, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(j, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RZ_gate(j, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(j, angle) circuit.add_CNOT_gate(i, j) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(i, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RZ_gate(i, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(i, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(j, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RZ_gate(j, angle) angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(j, angle) return circuit
[docs]def create_npqc_ansatz( n_qubit: int, c_depth: int = 4, c: float = 0.1 ) -> LearningCircuit: """ Creates circuit used in http://arxiv.org/abs/2108.01039, Fig. 5(a). Args: n_qubit: number of qubits. must be even. c_depth: circuit depth. The number of parameters is 8+4*(c_depth-1). c: hyperparameter of the circuit. Defined in Eq. (2) of the paper. """ if n_qubit % 2 != 0: raise ValueError( "create_large_qsv takes only integer number of qubits, but given " + str(n_qubit) ) def preprocess_x(x: NDArray[np.float_], index: int) -> float: xa: float = x[index % len(x)] return xa circuit = LearningCircuit(n_qubit) ban = 0 for i in range(n_qubit): circuit.add_input_RY_gate( i, lambda x, ban_lam=ban: preprocess_x(x, ban_lam) * c + np.pi / 2 ) ban = ban + 1 circuit.add_input_RZ_gate( i, lambda x, ban_lam=ban: preprocess_x(x, ban_lam) * c + np.pi / 2 ) ban = ban + 1 for c_kai in range(c_depth): for i in range(0, n_qubit - 1, 2): circuit.add_RY_gate(i, np.pi / 2) recC = c_kai + 1 recA = 0 while recC % 2 == 0: recC //= 2 recA += 1 circuit.add_gate(CZ(i, (i + recA * 2 + 1) % n_qubit)) circuit.add_input_RY_gate( i, lambda x, ban_lam=ban: preprocess_x(x, ban_lam) * c + np.pi / 2 ) ban = ban + 1 if c_kai + 1 < c_depth: circuit.add_input_RZ_gate( i, lambda x, ban_lam=ban: preprocess_x(x, ban_lam) * c + np.pi / 2, ) ban = ban + 1 return circuit
[docs]def create_yzcx_ansatz( n_qubit: int, c_depth: int = 4, c: float = 0.1, seed: Optional[int] = 0 ) -> LearningCircuit: """ Creates circuit used in http://arxiv.org/abs/2108.01039, Fig. 5(c). Args: n_qubit: number of qubits. must be even. c_depth: circuit depth. The number of parameters is 8*c_depth. c: hyperparameter of the circuit. Defined in Eq. (2) of the paper. """ def preprocess_x(x: NDArray[np.float_], index: int) -> float: xa: float = x[index % len(x)] return xa rng = default_rng(seed) circuit = LearningCircuit(n_qubit) ban = 0 for c_kai in range(c_depth): for i in range(0, n_qubit): angle = 2.0 * np.pi * rng.random() circuit.add_input_RY_gate( i, lambda x, ban_lam=ban: preprocess_x(x, ban_lam) * c ) circuit.add_parametric_RY_gate(i, angle) ban = ban + 1 angle = 2.0 * np.pi * rng.random() circuit.add_input_RZ_gate( i, lambda x, ban_lam=ban: preprocess_x(x, ban_lam) * c ) circuit.add_parametric_RZ_gate(i, angle) ban = ban + 1 if i % 2 == c_kai % 2 and i + 1 < n_qubit: circuit.add_CNOT_gate(i, i + 1) return circuit
[docs]def create_dqn_cl(n_qubit: int, c_depth: int, s_qubit: int) -> LearningCircuit: # from https://arxiv.org/abs/2112.15002 circuit = LearningCircuit(n_qubit) def preprocess_x(x: NDArray[np.float_], index: int) -> float: xa: float = x[index % len(x)] return xa for i in range(n_qubit): circuit.add_input_RY_gate(i, lambda x, i=i: preprocess_x(x, i)) circuit.add_parametric_RY_gate(i, 0.0) for _ in range(c_depth): for i in range(n_qubit): # 元の論文ではすべての組に対して張っていたっぽいが、それはゲート数が多すぎるだろ circuit.add_gate(CZ(i, (i + 1) % n_qubit)) # circuit.add_gate(CZ(n_qubit - 1, 0)) for i in range(s_qubit, n_qubit - 1): circuit.add_gate(CNOT(i, (i + 1) % n_qubit)) circuit.add_gate(CNOT(n_qubit - 1, s_qubit)) for i in range(n_qubit): circuit.add_parametric_RX_gate(i, 0.0) circuit.add_parametric_RY_gate(i, 0.0) circuit.add_parametric_RX_gate(i, 0.0) return circuit
[docs]def create_dqn_cl_no_cz(n_qubit: int, c_depth: int) -> LearningCircuit: # from https://arxiv.org/abs/2112.15002 circuit = LearningCircuit(n_qubit) def preprocess_x(x: NDArray[np.float_], index: int) -> float: xa = x[index % len(x)] return xa for i in range(n_qubit): circuit.add_input_RY_gate(i, lambda x, i=i: preprocess_x(x, i)) circuit.add_parametric_RY_gate(i, 0.0) for _ in range(c_depth): for i in range(n_qubit): circuit.add_gate(CNOT(i, (i + 1) % n_qubit)) circuit.add_parametric_RX_gate(i, 0.0) circuit.add_parametric_RY_gate(i, 0.0) circuit.add_parametric_RX_gate(i, 0.0) circuit.add_gate(CNOT(n_qubit - 1, 0)) return circuit
[docs]def create_qcnn_ansatz(n_qubit: int, seed: Optional[int] = 0) -> LearningCircuit: """ Creates circuit used in https://www.tensorflow.org/quantum/tutorials/qcnn?hl=en, Section 1. Args: n_qubit: number of qubits. must be even. seed: seed for random numbers. used for determining the interaction strength of the hamiltonian simulation """ rng = default_rng(seed) def one_qubit_unitary(circuit: LearningCircuit, index: int) -> List[int]: ids = [] angle = rng.uniform(-np.pi, np.pi) id = circuit.add_parametric_RX_gate(index, angle) ids.append(id) angle = rng.uniform(-np.pi, np.pi) id = circuit.add_parametric_RY_gate(index, angle) ids.append(id) angle = rng.uniform(-np.pi, np.pi) id = circuit.add_parametric_RZ_gate(index, angle) ids.append(id) return ids def _two_qubit_unitary( circuit: LearningCircuit, target: List[int], pauli_ids: List[int] ) -> LearningCircuit: angle = rng.uniform(-np.pi, np.pi) circuit.add_parametric_multi_Pauli_rotation_gate(target, pauli_ids, angle) return circuit def two_qubit_unitary( circuit: LearningCircuit, src: int, dest: int ) -> LearningCircuit: one_qubit_unitary(circuit, src) one_qubit_unitary(circuit, dest) target = [src, dest] pauli_xx_ids = [1, 1] circuit = _two_qubit_unitary(circuit, target, pauli_xx_ids) pauli_yy_ids = [2, 2] circuit = _two_qubit_unitary(circuit, target, pauli_yy_ids) pauli_zz_ids = [3, 3] circuit = _two_qubit_unitary(circuit, target, pauli_zz_ids) one_qubit_unitary(circuit, src) one_qubit_unitary(circuit, dest) return circuit def conv_circuit(circuit: LearningCircuit, src: int, dest: int) -> LearningCircuit: return two_qubit_unitary(circuit, src, dest) def pooling_circuit( circuit: LearningCircuit, src: int, dest: int ) -> LearningCircuit: ids = one_qubit_unitary(circuit, dest) one_qubit_unitary(circuit, src) circuit.add_CNOT_gate(src, dest) angle = rng.uniform(-np.pi, np.pi) circuit.add_parametric_RZ_gate( dest, angle, share_with=ids[2], share_with_coef=-1 ) circuit.add_parametric_RY_gate( dest, angle, share_with=ids[1], share_with_coef=-1 ) circuit.add_parametric_RX_gate( dest, angle, share_with=ids[0], share_with_coef=-1 ) return circuit circuit = LearningCircuit(n_qubit) for i in range(n_qubit): circuit.add_input_RX_gate(i, lambda x: x) # cluster state for i in range(n_qubit): circuit.add_H_gate(i) for this_bit in range(n_qubit): next_bit = this_bit + 1 if this_bit < n_qubit - 1 else 0 circuit.add_CNOT_gate(this_bit, next_bit) circuit.add_Z_gate(next_bit) targets = [] # 0始まりの数字のリストを受け取り # 二分木でペアを作ります # [0,1,2,3,4,5,6,7]を指定した場合 # [[0, 1], [2, 3], [1, 3], [4, 5], [6, 7], [5, 7], [3, 7]] になります。 # [0, 1],[2, 3],[4, 5],[6, 7]が枝となり、 # 次の階層の[1, 3],[5, 7]となります。階層の数字は下の層の通し番号が大きい方がペアになります。 # 最終的に[3, 7]の一番上の層が作られます。 # ツリー構造ですが、データはフラットな2次元配列になります。 def tree(ns: List[int]) -> dict: n = len(ns) if n <= 0: return node = {} node["ns"] = ns left = tree(ns[: n // 2]) right = tree(ns[n - (n // 2) :]) if left is not None and right is not None: targets.append([max(left["ns"]), max(right["ns"])]) return node tree([x for x in range(n_qubit)]) for t in targets: circuit = conv_circuit(circuit, t[0], t[1]) circuit = pooling_circuit(circuit, t[0], t[1]) return circuit
[docs]def create_multi_qubit_param_rotational_ansatz( n_qubit: int, c_depth: int = 1, seed: Optional[int] = 0 ) -> LearningCircuit: def preprocess_x(x: NDArray[np.float_], index: int) -> float: xa: float = x[index % len(x)] return xa rng = default_rng(seed) circuit = LearningCircuit(n_qubit) # embedding, at first just one time for qb in range(n_qubit): circuit.add_input_RY_gate(qb, lambda x, i=qb: preprocess_x(x, qb)) # now iterate over layers for _ in range(c_depth): for i in range(n_qubit): angle = 2.0 * np.pi * rng.random() circuit.add_parametric_RX_gate(i, 0.0) cops = rng.integers(1, 4, 2) circuit._add_multi_qubit_parametric_R_gate_inner( [i, (i + 1) % n_qubit], cops, angle, None, None ) return circuit