pqcprep.pqc_tools
Collection of functions relating to setting up a QCNN.
1""" 2Collection of functions relating to setting up a QCNN. 3""" 4import numpy as np 5from qiskit import QuantumCircuit, QuantumRegister, transpile 6from qiskit_aer import StatevectorSimulator 7from qiskit.circuit import ParameterVector 8from qiskit.circuit.library import U3Gate 9from itertools import combinations 10 11def N_gate(params, real=False): 12 r""" 13 Constructs the two-qubit three-parameter $\mathcal{N}$ gate, defined as 14 $$\mathcal{N}(\alpha, \beta, \gamma) = \exp \left( i \left[ \alpha X \otimes X + \beta Y \otimes Y + \gamma Z \otimes Z \right] \right),$$ 15 where $X$, $Y$, $Z$ are the Pauli operators. 16 17 This is implemented via three CX gates, three Rz gates and two Ry gates, as shown in [Vatan 2004](https://arxiv.org/pdf/quant-ph/0308006). 18 19 Arguments: 20 ---- 21 - **params** : *array_like* 22 23 Array-like object containing the gate parameters, corresponding to the qubit rotation angles. 24 25 - **real** : *boolean* 26 27 If True, a two-parameter version of the $\mathcal{N}$ gate is implemented instead, with one of the CX gates and all of the 28 Rz gates removed. This ensures real amplitudes. Default is False. 29 30 Returns: 31 ---- 32 - **circuit** : *QuantumCircuit* 33 34 Implementation of the $\mathcal{N}$ gate as a qiskit `QuantumCircuit`. 35 36 """ 37 38 if real: 39 circuit = QuantumCircuit(2, name="RN Gate") 40 circuit.cx(1, 0) 41 circuit.ry(params[0], 0) 42 circuit.ry(params[1], 1) 43 circuit.cx(0, 1) 44 else: 45 circuit = QuantumCircuit(2, name="N Gate") 46 circuit.rz(-np.pi / 2, 1) 47 circuit.cx(1, 0) 48 circuit.rz(params[0], 0) 49 circuit.ry(params[1], 1) 50 circuit.cx(0, 1) 51 circuit.ry(params[2], 1) 52 circuit.cx(1, 0) 53 circuit.rz(np.pi / 2, 0) 54 55 return circuit 56 57def input_layer(n, m, par_label, ctrl_state=0, real=False, params=None, AA=False, shift=0, wrap=False): 58 r""" 59 60 Implement a QCNN input layer as a parametrised quantum circuit. 61 62 The layer consists of a sequence of controlled arbitrary single-qubit operations (CU3 operations) 63 applied to the target register with the input qubits acting a controls. This feeds information 64 about the input register state into the target register. 65 66 Arguments: 67 ---- 68 - **n** : *int* 69 70 Number of qubits in the input register. 71 72 - **m** : *int* 73 74 Number of qubits in the target register. 75 76 - **par_label** : *str* 77 78 Label to assign to the parameter vector. 79 80 - **ctrl_state** : *int* 81 82 Control state of the controlled gates. If equal to 0, the controlled operation is applied 83 when the control qubit is in state $\ket{0}$. If equal to 1, the controlled operation is 84 applied when the control qubit is in state $\ket{1}$. Default is 0. 85 86 - **real** : *boolean* 87 88 If True, controlled Ry rotations are applied instead of CU3 operations. This ensures real 89 amplitudes. Default is False. 90 91 - **params** : *array_like*, *optional* 92 93 Directly assign values, stored in `params`, to the circuit parameters instead of creating a `ParameterVector`. 94 95 - **AA** : *boolean* 96 97 If True, each input qubit controls an operation on each target qubit, corresponding to an "all-to-all" layer topology. 98 Default is False. 99 100 - **shift** : *int* 101 102 If `AA` is False, the $j$th input qubit controls an operation applied on the $(j+s)$th target qubit with wrap-around 103 for `n > m`. Default is 0. 104 105 - **wrap**: *boolean* 106 107 If True, map rotation angles to an interval specified by `map_angle()`. Default is False. 108 109 Returns: 110 --- 111 - **circuit** : *QuantumCircuit* 112 113 The qiskit `QuantumCircuit` implementation of the input layer. 114 115 """ 116 117 # set up circuit 118 qc = QuantumCircuit(n+m, name="Input Layer") 119 qubits = list(range(n+m)) 120 121 # number of parameters used by each gate 122 num_par = 3 if real==False else 1 123 124 # number of gates applied per layer 125 num_gates = n if AA==False else n*m 126 127 # set up parameter vector 128 if params == None: 129 params = ParameterVector(par_label, length= num_par * num_gates) 130 param_index = 0 131 132 # define weight re-mapping function: 133 def wrap_angle(theta): 134 if wrap: 135 return map_angle(theta) 136 else: 137 return theta 138 139 # apply gates to qubits 140 if AA: 141 for i in qubits[:n]: 142 for j in qubits[n:]: 143 if real: 144 qc.cry(wrap_angle(params[int(param_index)]), qubits[i], qubits[j],ctrl_state=int(ctrl_state)) 145 param_index += 1 146 else: 147 par = params[int(param_index) : int(param_index + num_par)] 148 cu3 = U3Gate(wrap_angle(par[0]),wrap_angle(par[1]),wrap_angle(par[2])).control(1, ctrl_state=int(ctrl_state)) 149 qc.append(cu3, [qubits[i], qubits[j]]) 150 param_index += num_par 151 else: 152 for i in qubits[:n]: 153 154 j = i + shift 155 if np.modf(j/m)[1] >= 1: 156 j -=int(np.modf(j/m)[1] * m) 157 158 if real: 159 qc.cry(wrap_angle(params[i]), qubits[i], qubits[j+n], ctrl_state=int(ctrl_state)) 160 else: 161 par = params[int(param_index) : int(param_index + num_par)] 162 cu3 = U3Gate(wrap_angle(par[0]),wrap_angle(par[1]),wrap_angle(par[2])).control(1, ctrl_state=int(ctrl_state)) 163 qc.append(cu3, [qubits[i], qubits[j+n]]) 164 param_index += num_par 165 166 # package as instruction 167 qc_inst = qc.to_instruction() 168 circuit = QuantumCircuit(n+m) 169 circuit.append(qc_inst, qubits) 170 171 return circuit 172 173def conv_layer_NN(m, par_label, real=False, params=None, wrap=False): 174 """ 175 176 Implement a QCNN neighbour-to-neighbour convolutional layer as a parametrised quantum circuit. 177 178 The layer consists of the cascaded application of the two-qubit $\mathcal{N}$ gate on 179 the target register. $\mathcal{N}$ is applied to all neighbouring target qubits, including a connection between 180 the first and last qubit ("neighbour-to-neighbour" topology), resulting in a gate cost linear in the size of the target register. 181 182 Arguments: 183 ---- 184 - **m** : *int* 185 186 Number of qubits in the target register. 187 188 - **par_label** : *str* 189 190 Label to assign to the parameter vector. 191 192 - **real** : *boolean* 193 194 If True, the real version of the $\mathcal{N}$ gate is used (which only involves CX and Ry operations). This ensures real 195 amplitudes. Default is False. 196 197 - **params** : *array_like*, *optional* 198 199 Directly assign values, stored in `params`, to the circuit parameters instead of creating a `ParameterVector`. 200 201 - **wrap**: *boolean* 202 203 If True, map rotation angles to an interval specified by `map_angle()`. Default is False. 204 205 Returns: 206 --- 207 - **circuit** : *QuantumCircuit* 208 209 The qiskit `QuantumCircuit` implementation of the input layer. 210 """ 211 212 # set up circuit 213 qc = QuantumCircuit(m, name="Convolutional Layer (NN)") 214 qubits = list(range(m)) 215 216 # number of parameters used by each N gate 217 num_par = 3 if real==False else 2 218 219 # number of gates applied per layer 220 num_gates = m 221 222 # define weight re-mapping function: 223 def wrap_angle(theta): 224 if wrap: 225 return map_angle(theta) 226 else: 227 return theta 228 229 # set up parameter vector 230 param_index = 0 231 if params == None: 232 params = ParameterVector(par_label, length= int(num_par * num_gates)) 233 234 # apply N gate linearly between neighbouring qubits 235 # (including circular connection between last and first) 236 pairs = [tuple([i, i+1]) for i in qubits[:-1]] 237 pairs.append((qubits[-1], 0)) 238 239 for j in np.arange(num_gates): 240 pars = params[int(param_index) : int(param_index + num_par)] 241 qc.compose(N_gate([wrap_angle(i) for i in pars], real=real),pairs[int(j)],inplace=True) 242 param_index += num_par 243 244 # package as instruction 245 qc_inst = qc.to_instruction() 246 circuit = QuantumCircuit(m) 247 circuit.append(qc_inst, qubits) 248 249 return circuit 250 251def conv_layer_AA(m, par_label, real=False, params=None, wrap=False): 252 """ 253 254 Implement a QCNN all-to-all convolutional layer as a parametrised quantum circuit. 255 256 The layer consists of the cascaded application of the two-qubit $\mathcal{N}$ gate on 257 the target register. $\mathcal{N}$ is applied to all combinations of target qubits ("all-to-all" topology), 258 resulting in a gate cost quadratic in the size of the target register. 259 260 Arguments: 261 ---- 262 - **m** : *int* 263 264 Number of qubits in the target register. 265 266 - **par_label** : *str* 267 268 Label to assign to the parameter vector. 269 270 - **real** : *boolean* 271 272 If True, the real version of the $\mathcal{N}$ gate is used (which only involves CX and Ry operations). This ensures real 273 amplitudes. Default is False. 274 275 - **params** : *array_like*, *optional* 276 277 Directly assign values, stored in `params`, to the circuit parameters instead of creating a `ParameterVector`. 278 279 - **wrap**: *boolean* 280 281 If True, map rotation angles to an interval specified by `map_angle()`. Default is False. 282 283 Returns: 284 --- 285 - **circuit** : *QuantumCircuit* 286 287 The qiskit `QuantumCircuit` implementation of the input layer. 288 """ 289 290 # set up circuit 291 qc = QuantumCircuit(m, name="Convolutional Layer (AA)") 292 qubits = list(range(m)) 293 294 # number of parameters used by each N gate 295 num_par = 3 if real==False else 2 296 297 # number of gates applied per layer 298 num_gates = 0.5 * m * (m-1) 299 300 # define weight re-mapping function: 301 def wrap_angle(theta): 302 if wrap: 303 return map_angle(theta) 304 else: 305 return theta 306 307 # set up parameter vector 308 param_index = 0 309 if params == None: 310 params = ParameterVector(par_label, length= int(num_par * num_gates)) 311 312 # apply N gate linearly between neighbouring qubits 313 # (including circular connection between last and first) 314 pairs = list(combinations(qubits,2)) 315 316 for j in np.arange(num_gates): 317 pars = params[int(param_index) : int(param_index + num_par)] 318 qc.compose(N_gate([wrap_angle(i) for i in pars], real=real),pairs[int(j)],inplace=True) 319 if j != num_gates -1: 320 #qc.barrier() 321 s=" " 322 param_index += num_par 323 324 # package as instruction 325 qc_inst = qc.to_instruction() 326 circuit = QuantumCircuit(m) 327 circuit.append(qc_inst, qubits) 328 329 return circuit 330 331def digital_encoding(n): 332 r""" 333 Set up a parametrised quantum circuit for the digital encoding of a binary number onto 334 a quantum register. 335 336 The encoding is set by assigning the value 0 to the $i$th parameter of the circuit to 337 represent a state $\ket{0}$ for the $i$th bit and assigning $\pi$ for the case $\ket{1}$. 338 Values can be assigned to circuit parameters using qiskit's `QuantumCircuit.assign_parameters()`. 339 340 This is to be used in conjunction with the parameter array generated by `binary_to_encode_param()`. 341 342 Example usage: 343 344 >>> binary='011001' # bit string to encode 345 >>> params=binary_to_encode_param(binary) # generate parameter array 346 >>> qc=digital_encoding(len(binary)) # generate circuit 347 >>> qc.assign_parameters(params) # assign parameters to circuit 348 349 Arguments: 350 ---- 351 - **n** : *int* 352 353 Number of qubits in the register. Should be equal to the number of bits 354 in the bit strings that are to be encoded. 355 356 Returns: 357 ---- 358 - **circuit** : *QuantumCircuit* 359 360 The qiskit `QuantumCircuit` implementation of the encoding circuit, with `n` unassigned parameters. 361 362 """ 363 364 # set up circuit and parameter vector 365 qc = QuantumCircuit(n,name="Digital Encoding") 366 qubits = list(range(n)) 367 params = ParameterVector("enc", length=n) 368 369 # flip ith qubit if params[i]==np.pi 370 for i in np.arange(n): 371 qc.rx(params[i], qubits[i]) 372 qc.p(params[i]/2, qubits[i]) 373 374 # package as instruction 375 qc_inst = qc.to_instruction() 376 circuit = QuantumCircuit(n) 377 circuit.append(qc_inst, qubits) 378 379 return circuit 380 381def binary_to_encode_param(binary): 382 """ 383 384 Generate the parameter array for digital encoding corresponding to a binary string. 385 386 This is to be used in conjunction with the encoding circuit generated by `digital_encoding()`. 387 388 Example usage: 389 390 >>> binary='011001' # bit string to encode 391 >>> params=binary_to_encode_param(binary) # generate parameter array 392 >>> qc=digital_encoding(len(binary)) # generate circuit 393 >>> qc.assign_parameters(params) # assign parameters to circuit 394 395 Arguments: 396 ---- 397 - **binary** : *str* 398 399 The binary string. Little endian convention is assumed. 400 401 Returns: 402 ---- 403 - **params** : *array_like* 404 405 The parameter array of length `len(binary)` corresponding to `binary`. Big endian convention is used. 406 407 408 Convert an n-bit binary string to the associated parameter array to feed 409 into the digital encoding circuit. 410 """ 411 412 params = np.empty(len(binary)) 413 414 binary = binary[::-1] # reverse binary string (small endian for binary, big endian for arrays) 415 416 for i in np.arange(len(binary)): 417 if binary[i]=="0": 418 params[i]=0 419 elif binary[i]=="1": 420 params[i]=np.pi 421 else: 422 raise ValueError("Binary string should only include characters '0' and '1'.") 423 424 return params 425 426def generate_network(n,m,L, encode=False, toggle_IL=True, initial_IL=True, input_Ry=False, real=False, inverse=False, repeat_params=None, wrap=False): 427 r""" 428 Set up a QCNN consisting of input and convolutional layers acting on two distinct registers, the 'input register' and the 'target register'. 429 430 Input layers consist of single-qubit operations applied to the target register, controlled by the input register qubits. Convolutional layers 431 consist of two-qubit operations applied to the target register. See `input_layer()` for more information on input layers and `conv_layer_AA()`, 432 `conv_layer_NN()` for more information on convolutional layers. 433 434 The network was designed for the task of performing function evaluation, i.e. to implement an operator $\hat{Q}_\Psi$ such that 435 $$ \hat{Q}_\Psi \ket{j}_i \ket{0}_t = \ket{j}_i \ket{\Psi(j)}_t,$$ 436 for some function $\Psi$ with the subscripts $i$ and $t$ denoting the input and target registers, respectively. 437 With some adaptation, the network structure could also be used for other applications. 438 439 Arguments: 440 --- 441 - **n** : *int* 442 443 Number of qubits in the input register. 444 445 - **m** : *int* 446 447 Number of qubits in the target register. 448 449 - **L** : *int* 450 451 Number of layers in the network. Note that `L` does not take into account the optional initial input layer 452 added by `initial_IL`. Further, `L` does not take into account the padding of the network with additional input 453 layers to ensure the number of input layers is at least equal to `m`. 454 455 - **encode** : *boolean* 456 457 If True, apply an initial encoding circuit to the input register which can be used control the input states of the network. 458 Default is False. 459 460 - **toggle_IL** : *boolean* 461 462 If True, every third layer added is an input layer and additional input layers are added to ensure the number of input layers is at least equal to 463 `m`, with input layers alternating between control states 0 and 1. The input layer `shift` parameter is successively increased for each new input layer, 464 resulting in each input qubit controlling an operation on each target qubit at some point in the network. 465 466 If False, only convolutional layers are added. In either case, convolutional layers alternate 467 between all-to-all and neighbour-to-neighbour layers. Default is True. 468 469 - **initial_IL** : *boolean* 470 471 If True, add an input layer at the beginning of the circuit. Default is True. 472 473 - **input_Ry** : *boolean* 474 475 If True, initially apply a sequence of parametrised Ry rotations to the input register. Default is False. 476 477 - **real** : *boolean* 478 479 If True, use the real versions of input and convolutional layers, which only contain CX and Ry gates and hence ensure 480 real amplitudes. Default is False. 481 482 - **inverse** : *boolean* 483 484 If True, invert the circuit and return the inverse of the network. 485 486 - **repeat_params** : *str*, *optional* 487 488 Keep parameters fixed for different layer types, i.e. use the same parameter values for each instance of a layer type. 489 Options are `'CL'` (keep parameters fixed for convolutional layers), `'IL'` (keep parameters fixed for input layers), `'both'` 490 (keep parameters fixed for both convolutional and input layers). 491 492 - **wrap**: *boolean* 493 494 If True, map rotation angles to an interval specified by `map_angle()`. Default is False. 495 496 497 Returns: 498 ---- 499 - **circuit** : *QuantumCircuit* 500 501 The qiskit `QuantumCircuit` implementation of the network, with unassigned parameters. 502 503 """ 504 # initialise parameter vector 505 if repeat_params==None: 506 AA_CL_params=None 507 NN_CL_params=None 508 IL_params=None 509 elif repeat_params=="CL": 510 IL_params=None 511 AA_CL_params=ParameterVector(u"\u03B8_CL_AA", length= int((3 if real==False else 2 ) * (0.5 * m * (m-1)))) 512 NN_CL_params=ParameterVector(u"\u03B8_CL_NN", length= int((3 if real==False else 2) * m)) 513 elif repeat_params=="IL": 514 IL_params=ParameterVector(u"\u03B8_IL", length= int((3 if real==False else 1) * n)) 515 AA_CL_params=None 516 NN_CL_params=None 517 elif repeat_params=="both": 518 AA_CL_params=ParameterVector(u"\u03B8_CL_AA", length= int((3 if real==False else 2 ) * (0.5 * m * (m-1)))) 519 NN_CL_params=ParameterVector(u"\u03B8_CL_NN", length= int((3 if real==False else 2) * m)) 520 IL_params=ParameterVector(u"\u03B8_IL", length= int((3 if real==False else 1) * n)) 521 else: 522 raise ValueError("Unrecognised option for 'repeat_params'. Should be None, 'CL', 'IL', or 'both'.") 523 524 # initialise empty input and target registers 525 input_register = QuantumRegister(n, "input") 526 target_register = QuantumRegister(m, "target") 527 circuit = QuantumCircuit(input_register, target_register) 528 529 # prepare registers 530 circuit.h(target_register) 531 if encode: 532 circuit.compose(digital_encoding(n), input_register, inplace=True) 533 534 if input_Ry: 535 input_Ry_params = ParameterVector("Input_Ry",n) 536 for i in np.arange(n): 537 circuit.ry(input_Ry_params[i], input_register[i]) 538 539 if initial_IL: 540 # apply input layer 541 circuit.compose(input_layer(n,m, u"\u03B8_IL_0", real=real, params=IL_params, wrap=wrap), circuit.qubits, inplace=True) 542 543 # apply convolutional layers (alternating between AA and NN) 544 # if toggle_IL is True, additional input layers are added after each NN 545 for i in np.arange(L): 546 547 if toggle_IL==False: 548 549 if i % 2 ==0: 550 circuit.compose(conv_layer_AA(m, u"\u03B8_CL_AA_{0}".format(i // 2), real=real, params=AA_CL_params,wrap=wrap), target_register, inplace=True) 551 elif i % 2 ==1: 552 circuit.compose(conv_layer_NN(m, u"\u03B8_CL_NN_{0}".format(i // 2),real=real, params=NN_CL_params, wrap=wrap), target_register, inplace=True) 553 554 if toggle_IL==True: 555 556 # difference between number of input layers and number of target qubits: 557 del_IL = m - (int(initial_IL)+ L //3) 558 559 if del_IL <= 0: 560 if i % 3 ==0: 561 circuit.compose(conv_layer_AA(m, u"\u03B8_CL_AA_{0}".format(i // 3),real=real,params=AA_CL_params, wrap=wrap), target_register, inplace=True) 562 elif i % 3 ==1: 563 circuit.compose(conv_layer_NN(m, u"\u03B8_CL_NN_{0}".format(i // 3),real=real,params=NN_CL_params, wrap=wrap), target_register, inplace=True) 564 elif i % 3 ==2: 565 circuit.compose(input_layer(n,m, u"\u03B8_IL_{0}".format(i // 3 +1),shift=(i//3)+1, ctrl_state=(i % 2),real=real,params=IL_params, wrap=wrap), circuit.qubits, inplace=True) 566 else: 567 if i % 3 ==0: 568 circuit.compose(conv_layer_AA(m, u"\u03B8_CL_AA_{0}".format(i // 3),real=real,params=AA_CL_params, wrap=wrap), target_register, inplace=True) 569 elif i % 3 ==1: 570 circuit.compose(conv_layer_NN(m, u"\u03B8_CL_NN_{0}".format(i // 3),real=real,params=NN_CL_params, wrap=wrap), target_register, inplace=True) 571 elif i % 3 ==2: 572 # padd with additional input layers 573 for j in np.arange(del_IL // (L //3) +2): 574 shift = (i // 3) +int(initial_IL) + j * (L //3) 575 if shift <= m-1: 576 circuit.compose(input_layer(n,m, u"\u03B8_IL_{0}".format(shift),shift=shift, ctrl_state=((shift-int(initial_IL)) % 2),real=real,params=IL_params, wrap=wrap), circuit.qubits, inplace=True) 577 578 if inverse: 579 circuit=circuit.inverse() 580 581 return circuit 582 583def A_generate_network(n,L, repeat_params=False, wrap=False): 584 r""" 585 Set up a network consisting of real convolutional layers acting on a single qubit register. 586 587 Layers alternate between all-to-all and neighbour-to-neighbour convolutional layers. For more 588 information see `conv_layer_AA()`, `conv_layer_NN()`. The convolutional layers are *real* in 589 the sense of involving only CX and Ry operations, ensuring real amplitudes. 590 591 The network was designed for the task of encoding a suitably normalised real function, $A(j)$, into the amplitudes of the 592 register, i.e. to implement an operator $\hat{U}_A$ such that 593 $$ \hat{U}_A \ket{j}= A(j) \ket{j}.$$ 594 With some adaptation, the network structure could also be used for other applications. 595 596 Arguments: 597 --- 598 - **n** : *int* 599 600 Number of qubits in the register. 601 602 - **L** : *int* 603 604 Number of layers in the network. 605 606 - **repeat_params** : *boolean* 607 608 If True, use the same parameter values for each layer type. Default is False. 609 610 - **wrap**: *boolean* 611 612 If True, map rotation angles to an interval specified by `map_angle()`. Default is False. 613 614 Returns: 615 --- 616 - **circuit** : *QuantumCircuit* 617 618 The qiskit `QuantumCircuit` implementation of the network, with unassigned parameters. 619 620 """ 621 622 # initialise parameter vector 623 if repeat_params: 624 AA_CL_params=ParameterVector(u"\u03B8_AA", length= int(2 * (0.5 * n * (n-1)))) 625 NN_CL_params=ParameterVector(u"\u03B8_NN", length= int(2 * n)) 626 else: 627 AA_CL_params = None 628 NN_CL_params = None 629 630 631 # initialise empty input register 632 register = QuantumRegister(n, "reg") 633 circuit = QuantumCircuit(register) 634 635 # prepare register 636 circuit.h(register) 637 638 # apply L convolutional layers (alternating between AA and NN) 639 for i in np.arange(L): 640 641 if i % 2 ==0: 642 circuit.compose(conv_layer_AA(n, u"\u03B8_R_AA_{0}".format(i // 2), real=True, params=AA_CL_params,wrap=wrap), register, inplace=True) 643 elif i % 2 ==1: 644 circuit.compose(conv_layer_NN(n, u"\u03B8_R_NN_{0}".format(i // 2), real=True, params=NN_CL_params,wrap=wrap), register, inplace=True) 645 646 return circuit 647 648def get_state_vec(circuit): 649 """ 650 Get statevector produced by a quantum circuit. 651 652 Uses the `qiskit_aer` backend. 653 654 Arguments: 655 ---- 656 - **circuit** : *QuantumCircuit* 657 658 The circuit to be evaluated. 659 660 Returns: 661 ---- 662 - **state_vector** : *array_like* 663 664 Array storing the complex amplitudes of the system to be in each of the 665 `2**circuit.num_qubits` basis states. 666 667 """ 668 669 # Transpile for simulator 670 simulator = StatevectorSimulator() 671 circuit = transpile(circuit, simulator) 672 673 # Run and get counts 674 result = simulator.run(circuit).result() 675 state_vector= result.get_statevector(circuit) 676 677 return np.asarray(state_vector) 678 679def map_angle(theta): 680 r""" 681 Map a rotation angle to the interval $(0, \frac{\pi}{2})$. 682 683 An inverse tan function is used for the mapping. 684 685 Arguments: 686 ---- 687 - **theta** : *float* 688 689 Rotation angle. 690 691 Returns: 692 --- 693 - **theta_mapped** : *float* 694 695 Rotation angle mapped to the given interval. 696 697 """ 698 699 return np.arctan(theta) + np.pi /2
12def N_gate(params, real=False): 13 r""" 14 Constructs the two-qubit three-parameter $\mathcal{N}$ gate, defined as 15 $$\mathcal{N}(\alpha, \beta, \gamma) = \exp \left( i \left[ \alpha X \otimes X + \beta Y \otimes Y + \gamma Z \otimes Z \right] \right),$$ 16 where $X$, $Y$, $Z$ are the Pauli operators. 17 18 This is implemented via three CX gates, three Rz gates and two Ry gates, as shown in [Vatan 2004](https://arxiv.org/pdf/quant-ph/0308006). 19 20 Arguments: 21 ---- 22 - **params** : *array_like* 23 24 Array-like object containing the gate parameters, corresponding to the qubit rotation angles. 25 26 - **real** : *boolean* 27 28 If True, a two-parameter version of the $\mathcal{N}$ gate is implemented instead, with one of the CX gates and all of the 29 Rz gates removed. This ensures real amplitudes. Default is False. 30 31 Returns: 32 ---- 33 - **circuit** : *QuantumCircuit* 34 35 Implementation of the $\mathcal{N}$ gate as a qiskit `QuantumCircuit`. 36 37 """ 38 39 if real: 40 circuit = QuantumCircuit(2, name="RN Gate") 41 circuit.cx(1, 0) 42 circuit.ry(params[0], 0) 43 circuit.ry(params[1], 1) 44 circuit.cx(0, 1) 45 else: 46 circuit = QuantumCircuit(2, name="N Gate") 47 circuit.rz(-np.pi / 2, 1) 48 circuit.cx(1, 0) 49 circuit.rz(params[0], 0) 50 circuit.ry(params[1], 1) 51 circuit.cx(0, 1) 52 circuit.ry(params[2], 1) 53 circuit.cx(1, 0) 54 circuit.rz(np.pi / 2, 0) 55 56 return circuit
Constructs the two-qubit three-parameter $\mathcal{N}$ gate, defined as $$\mathcal{N}(\alpha, \beta, \gamma) = \exp \left( i \left[ \alpha X \otimes X + \beta Y \otimes Y + \gamma Z \otimes Z \right] \right),$$ where $X$, $Y$, $Z$ are the Pauli operators.
This is implemented via three CX gates, three Rz gates and two Ry gates, as shown in Vatan 2004.
Arguments:
params : array_like
Array-like object containing the gate parameters, corresponding to the qubit rotation angles.
real : boolean
If True, a two-parameter version of the $\mathcal{N}$ gate is implemented instead, with one of the CX gates and all of the Rz gates removed. This ensures real amplitudes. Default is False.
Returns:
circuit : QuantumCircuit
Implementation of the $\mathcal{N}$ gate as a qiskit
QuantumCircuit.
58def input_layer(n, m, par_label, ctrl_state=0, real=False, params=None, AA=False, shift=0, wrap=False): 59 r""" 60 61 Implement a QCNN input layer as a parametrised quantum circuit. 62 63 The layer consists of a sequence of controlled arbitrary single-qubit operations (CU3 operations) 64 applied to the target register with the input qubits acting a controls. This feeds information 65 about the input register state into the target register. 66 67 Arguments: 68 ---- 69 - **n** : *int* 70 71 Number of qubits in the input register. 72 73 - **m** : *int* 74 75 Number of qubits in the target register. 76 77 - **par_label** : *str* 78 79 Label to assign to the parameter vector. 80 81 - **ctrl_state** : *int* 82 83 Control state of the controlled gates. If equal to 0, the controlled operation is applied 84 when the control qubit is in state $\ket{0}$. If equal to 1, the controlled operation is 85 applied when the control qubit is in state $\ket{1}$. Default is 0. 86 87 - **real** : *boolean* 88 89 If True, controlled Ry rotations are applied instead of CU3 operations. This ensures real 90 amplitudes. Default is False. 91 92 - **params** : *array_like*, *optional* 93 94 Directly assign values, stored in `params`, to the circuit parameters instead of creating a `ParameterVector`. 95 96 - **AA** : *boolean* 97 98 If True, each input qubit controls an operation on each target qubit, corresponding to an "all-to-all" layer topology. 99 Default is False. 100 101 - **shift** : *int* 102 103 If `AA` is False, the $j$th input qubit controls an operation applied on the $(j+s)$th target qubit with wrap-around 104 for `n > m`. Default is 0. 105 106 - **wrap**: *boolean* 107 108 If True, map rotation angles to an interval specified by `map_angle()`. Default is False. 109 110 Returns: 111 --- 112 - **circuit** : *QuantumCircuit* 113 114 The qiskit `QuantumCircuit` implementation of the input layer. 115 116 """ 117 118 # set up circuit 119 qc = QuantumCircuit(n+m, name="Input Layer") 120 qubits = list(range(n+m)) 121 122 # number of parameters used by each gate 123 num_par = 3 if real==False else 1 124 125 # number of gates applied per layer 126 num_gates = n if AA==False else n*m 127 128 # set up parameter vector 129 if params == None: 130 params = ParameterVector(par_label, length= num_par * num_gates) 131 param_index = 0 132 133 # define weight re-mapping function: 134 def wrap_angle(theta): 135 if wrap: 136 return map_angle(theta) 137 else: 138 return theta 139 140 # apply gates to qubits 141 if AA: 142 for i in qubits[:n]: 143 for j in qubits[n:]: 144 if real: 145 qc.cry(wrap_angle(params[int(param_index)]), qubits[i], qubits[j],ctrl_state=int(ctrl_state)) 146 param_index += 1 147 else: 148 par = params[int(param_index) : int(param_index + num_par)] 149 cu3 = U3Gate(wrap_angle(par[0]),wrap_angle(par[1]),wrap_angle(par[2])).control(1, ctrl_state=int(ctrl_state)) 150 qc.append(cu3, [qubits[i], qubits[j]]) 151 param_index += num_par 152 else: 153 for i in qubits[:n]: 154 155 j = i + shift 156 if np.modf(j/m)[1] >= 1: 157 j -=int(np.modf(j/m)[1] * m) 158 159 if real: 160 qc.cry(wrap_angle(params[i]), qubits[i], qubits[j+n], ctrl_state=int(ctrl_state)) 161 else: 162 par = params[int(param_index) : int(param_index + num_par)] 163 cu3 = U3Gate(wrap_angle(par[0]),wrap_angle(par[1]),wrap_angle(par[2])).control(1, ctrl_state=int(ctrl_state)) 164 qc.append(cu3, [qubits[i], qubits[j+n]]) 165 param_index += num_par 166 167 # package as instruction 168 qc_inst = qc.to_instruction() 169 circuit = QuantumCircuit(n+m) 170 circuit.append(qc_inst, qubits) 171 172 return circuit
Implement a QCNN input layer as a parametrised quantum circuit.
The layer consists of a sequence of controlled arbitrary single-qubit operations (CU3 operations) applied to the target register with the input qubits acting a controls. This feeds information about the input register state into the target register.
Arguments:
n : int
Number of qubits in the input register.
m : int
Number of qubits in the target register.
par_label : str
Label to assign to the parameter vector.
ctrl_state : int
Control state of the controlled gates. If equal to 0, the controlled operation is applied when the control qubit is in state $\ket{0}$. If equal to 1, the controlled operation is applied when the control qubit is in state $\ket{1}$. Default is 0.
real : boolean
If True, controlled Ry rotations are applied instead of CU3 operations. This ensures real amplitudes. Default is False.
params : array_like, optional
Directly assign values, stored in
params, to the circuit parameters instead of creating aParameterVector.AA : boolean
If True, each input qubit controls an operation on each target qubit, corresponding to an "all-to-all" layer topology. Default is False.
shift : int
If
AAis False, the $j$th input qubit controls an operation applied on the $(j+s)$th target qubit with wrap-around forn > m. Default is 0.wrap: boolean
If True, map rotation angles to an interval specified by
map_angle(). Default is False.
Returns:
circuit : QuantumCircuit
The qiskit
QuantumCircuitimplementation of the input layer.
174def conv_layer_NN(m, par_label, real=False, params=None, wrap=False): 175 """ 176 177 Implement a QCNN neighbour-to-neighbour convolutional layer as a parametrised quantum circuit. 178 179 The layer consists of the cascaded application of the two-qubit $\mathcal{N}$ gate on 180 the target register. $\mathcal{N}$ is applied to all neighbouring target qubits, including a connection between 181 the first and last qubit ("neighbour-to-neighbour" topology), resulting in a gate cost linear in the size of the target register. 182 183 Arguments: 184 ---- 185 - **m** : *int* 186 187 Number of qubits in the target register. 188 189 - **par_label** : *str* 190 191 Label to assign to the parameter vector. 192 193 - **real** : *boolean* 194 195 If True, the real version of the $\mathcal{N}$ gate is used (which only involves CX and Ry operations). This ensures real 196 amplitudes. Default is False. 197 198 - **params** : *array_like*, *optional* 199 200 Directly assign values, stored in `params`, to the circuit parameters instead of creating a `ParameterVector`. 201 202 - **wrap**: *boolean* 203 204 If True, map rotation angles to an interval specified by `map_angle()`. Default is False. 205 206 Returns: 207 --- 208 - **circuit** : *QuantumCircuit* 209 210 The qiskit `QuantumCircuit` implementation of the input layer. 211 """ 212 213 # set up circuit 214 qc = QuantumCircuit(m, name="Convolutional Layer (NN)") 215 qubits = list(range(m)) 216 217 # number of parameters used by each N gate 218 num_par = 3 if real==False else 2 219 220 # number of gates applied per layer 221 num_gates = m 222 223 # define weight re-mapping function: 224 def wrap_angle(theta): 225 if wrap: 226 return map_angle(theta) 227 else: 228 return theta 229 230 # set up parameter vector 231 param_index = 0 232 if params == None: 233 params = ParameterVector(par_label, length= int(num_par * num_gates)) 234 235 # apply N gate linearly between neighbouring qubits 236 # (including circular connection between last and first) 237 pairs = [tuple([i, i+1]) for i in qubits[:-1]] 238 pairs.append((qubits[-1], 0)) 239 240 for j in np.arange(num_gates): 241 pars = params[int(param_index) : int(param_index + num_par)] 242 qc.compose(N_gate([wrap_angle(i) for i in pars], real=real),pairs[int(j)],inplace=True) 243 param_index += num_par 244 245 # package as instruction 246 qc_inst = qc.to_instruction() 247 circuit = QuantumCircuit(m) 248 circuit.append(qc_inst, qubits) 249 250 return circuit
Implement a QCNN neighbour-to-neighbour convolutional layer as a parametrised quantum circuit.
The layer consists of the cascaded application of the two-qubit $\mathcal{N}$ gate on the target register. $\mathcal{N}$ is applied to all neighbouring target qubits, including a connection between the first and last qubit ("neighbour-to-neighbour" topology), resulting in a gate cost linear in the size of the target register.
Arguments:
m : int
Number of qubits in the target register.
par_label : str
Label to assign to the parameter vector.
real : boolean
If True, the real version of the $\mathcal{N}$ gate is used (which only involves CX and Ry operations). This ensures real amplitudes. Default is False.
params : array_like, optional
Directly assign values, stored in
params, to the circuit parameters instead of creating aParameterVector.wrap: boolean
If True, map rotation angles to an interval specified by
map_angle(). Default is False.
Returns:
circuit : QuantumCircuit
The qiskit
QuantumCircuitimplementation of the input layer.
252def conv_layer_AA(m, par_label, real=False, params=None, wrap=False): 253 """ 254 255 Implement a QCNN all-to-all convolutional layer as a parametrised quantum circuit. 256 257 The layer consists of the cascaded application of the two-qubit $\mathcal{N}$ gate on 258 the target register. $\mathcal{N}$ is applied to all combinations of target qubits ("all-to-all" topology), 259 resulting in a gate cost quadratic in the size of the target register. 260 261 Arguments: 262 ---- 263 - **m** : *int* 264 265 Number of qubits in the target register. 266 267 - **par_label** : *str* 268 269 Label to assign to the parameter vector. 270 271 - **real** : *boolean* 272 273 If True, the real version of the $\mathcal{N}$ gate is used (which only involves CX and Ry operations). This ensures real 274 amplitudes. Default is False. 275 276 - **params** : *array_like*, *optional* 277 278 Directly assign values, stored in `params`, to the circuit parameters instead of creating a `ParameterVector`. 279 280 - **wrap**: *boolean* 281 282 If True, map rotation angles to an interval specified by `map_angle()`. Default is False. 283 284 Returns: 285 --- 286 - **circuit** : *QuantumCircuit* 287 288 The qiskit `QuantumCircuit` implementation of the input layer. 289 """ 290 291 # set up circuit 292 qc = QuantumCircuit(m, name="Convolutional Layer (AA)") 293 qubits = list(range(m)) 294 295 # number of parameters used by each N gate 296 num_par = 3 if real==False else 2 297 298 # number of gates applied per layer 299 num_gates = 0.5 * m * (m-1) 300 301 # define weight re-mapping function: 302 def wrap_angle(theta): 303 if wrap: 304 return map_angle(theta) 305 else: 306 return theta 307 308 # set up parameter vector 309 param_index = 0 310 if params == None: 311 params = ParameterVector(par_label, length= int(num_par * num_gates)) 312 313 # apply N gate linearly between neighbouring qubits 314 # (including circular connection between last and first) 315 pairs = list(combinations(qubits,2)) 316 317 for j in np.arange(num_gates): 318 pars = params[int(param_index) : int(param_index + num_par)] 319 qc.compose(N_gate([wrap_angle(i) for i in pars], real=real),pairs[int(j)],inplace=True) 320 if j != num_gates -1: 321 #qc.barrier() 322 s=" " 323 param_index += num_par 324 325 # package as instruction 326 qc_inst = qc.to_instruction() 327 circuit = QuantumCircuit(m) 328 circuit.append(qc_inst, qubits) 329 330 return circuit
Implement a QCNN all-to-all convolutional layer as a parametrised quantum circuit.
The layer consists of the cascaded application of the two-qubit $\mathcal{N}$ gate on the target register. $\mathcal{N}$ is applied to all combinations of target qubits ("all-to-all" topology), resulting in a gate cost quadratic in the size of the target register.
Arguments:
m : int
Number of qubits in the target register.
par_label : str
Label to assign to the parameter vector.
real : boolean
If True, the real version of the $\mathcal{N}$ gate is used (which only involves CX and Ry operations). This ensures real amplitudes. Default is False.
params : array_like, optional
Directly assign values, stored in
params, to the circuit parameters instead of creating aParameterVector.wrap: boolean
If True, map rotation angles to an interval specified by
map_angle(). Default is False.
Returns:
circuit : QuantumCircuit
The qiskit
QuantumCircuitimplementation of the input layer.
332def digital_encoding(n): 333 r""" 334 Set up a parametrised quantum circuit for the digital encoding of a binary number onto 335 a quantum register. 336 337 The encoding is set by assigning the value 0 to the $i$th parameter of the circuit to 338 represent a state $\ket{0}$ for the $i$th bit and assigning $\pi$ for the case $\ket{1}$. 339 Values can be assigned to circuit parameters using qiskit's `QuantumCircuit.assign_parameters()`. 340 341 This is to be used in conjunction with the parameter array generated by `binary_to_encode_param()`. 342 343 Example usage: 344 345 >>> binary='011001' # bit string to encode 346 >>> params=binary_to_encode_param(binary) # generate parameter array 347 >>> qc=digital_encoding(len(binary)) # generate circuit 348 >>> qc.assign_parameters(params) # assign parameters to circuit 349 350 Arguments: 351 ---- 352 - **n** : *int* 353 354 Number of qubits in the register. Should be equal to the number of bits 355 in the bit strings that are to be encoded. 356 357 Returns: 358 ---- 359 - **circuit** : *QuantumCircuit* 360 361 The qiskit `QuantumCircuit` implementation of the encoding circuit, with `n` unassigned parameters. 362 363 """ 364 365 # set up circuit and parameter vector 366 qc = QuantumCircuit(n,name="Digital Encoding") 367 qubits = list(range(n)) 368 params = ParameterVector("enc", length=n) 369 370 # flip ith qubit if params[i]==np.pi 371 for i in np.arange(n): 372 qc.rx(params[i], qubits[i]) 373 qc.p(params[i]/2, qubits[i]) 374 375 # package as instruction 376 qc_inst = qc.to_instruction() 377 circuit = QuantumCircuit(n) 378 circuit.append(qc_inst, qubits) 379 380 return circuit
Set up a parametrised quantum circuit for the digital encoding of a binary number onto a quantum register.
The encoding is set by assigning the value 0 to the $i$th parameter of the circuit to
represent a state $\ket{0}$ for the $i$th bit and assigning $\pi$ for the case $\ket{1}$.
Values can be assigned to circuit parameters using qiskit's QuantumCircuit.assign_parameters().
This is to be used in conjunction with the parameter array generated by binary_to_encode_param().
Example usage:
>>> binary='011001' # bit string to encode
>>> params=binary_to_encode_param(binary) # generate parameter array
>>> qc=digital_encoding(len(binary)) # generate circuit
>>> qc.assign_parameters(params) # assign parameters to circuit
Arguments:
n : int
Number of qubits in the register. Should be equal to the number of bits in the bit strings that are to be encoded.
Returns:
circuit : QuantumCircuit
The qiskit
QuantumCircuitimplementation of the encoding circuit, withnunassigned parameters.
382def binary_to_encode_param(binary): 383 """ 384 385 Generate the parameter array for digital encoding corresponding to a binary string. 386 387 This is to be used in conjunction with the encoding circuit generated by `digital_encoding()`. 388 389 Example usage: 390 391 >>> binary='011001' # bit string to encode 392 >>> params=binary_to_encode_param(binary) # generate parameter array 393 >>> qc=digital_encoding(len(binary)) # generate circuit 394 >>> qc.assign_parameters(params) # assign parameters to circuit 395 396 Arguments: 397 ---- 398 - **binary** : *str* 399 400 The binary string. Little endian convention is assumed. 401 402 Returns: 403 ---- 404 - **params** : *array_like* 405 406 The parameter array of length `len(binary)` corresponding to `binary`. Big endian convention is used. 407 408 409 Convert an n-bit binary string to the associated parameter array to feed 410 into the digital encoding circuit. 411 """ 412 413 params = np.empty(len(binary)) 414 415 binary = binary[::-1] # reverse binary string (small endian for binary, big endian for arrays) 416 417 for i in np.arange(len(binary)): 418 if binary[i]=="0": 419 params[i]=0 420 elif binary[i]=="1": 421 params[i]=np.pi 422 else: 423 raise ValueError("Binary string should only include characters '0' and '1'.") 424 425 return params
Generate the parameter array for digital encoding corresponding to a binary string.
This is to be used in conjunction with the encoding circuit generated by digital_encoding().
Example usage:
>>> binary='011001' # bit string to encode
>>> params=binary_to_encode_param(binary) # generate parameter array
>>> qc=digital_encoding(len(binary)) # generate circuit
>>> qc.assign_parameters(params) # assign parameters to circuit
Arguments:
binary : str
The binary string. Little endian convention is assumed.
Returns:
params : array_like
The parameter array of length
len(binary)corresponding tobinary. Big endian convention is used.
Convert an n-bit binary string to the associated parameter array to feed into the digital encoding circuit.
427def generate_network(n,m,L, encode=False, toggle_IL=True, initial_IL=True, input_Ry=False, real=False, inverse=False, repeat_params=None, wrap=False): 428 r""" 429 Set up a QCNN consisting of input and convolutional layers acting on two distinct registers, the 'input register' and the 'target register'. 430 431 Input layers consist of single-qubit operations applied to the target register, controlled by the input register qubits. Convolutional layers 432 consist of two-qubit operations applied to the target register. See `input_layer()` for more information on input layers and `conv_layer_AA()`, 433 `conv_layer_NN()` for more information on convolutional layers. 434 435 The network was designed for the task of performing function evaluation, i.e. to implement an operator $\hat{Q}_\Psi$ such that 436 $$ \hat{Q}_\Psi \ket{j}_i \ket{0}_t = \ket{j}_i \ket{\Psi(j)}_t,$$ 437 for some function $\Psi$ with the subscripts $i$ and $t$ denoting the input and target registers, respectively. 438 With some adaptation, the network structure could also be used for other applications. 439 440 Arguments: 441 --- 442 - **n** : *int* 443 444 Number of qubits in the input register. 445 446 - **m** : *int* 447 448 Number of qubits in the target register. 449 450 - **L** : *int* 451 452 Number of layers in the network. Note that `L` does not take into account the optional initial input layer 453 added by `initial_IL`. Further, `L` does not take into account the padding of the network with additional input 454 layers to ensure the number of input layers is at least equal to `m`. 455 456 - **encode** : *boolean* 457 458 If True, apply an initial encoding circuit to the input register which can be used control the input states of the network. 459 Default is False. 460 461 - **toggle_IL** : *boolean* 462 463 If True, every third layer added is an input layer and additional input layers are added to ensure the number of input layers is at least equal to 464 `m`, with input layers alternating between control states 0 and 1. The input layer `shift` parameter is successively increased for each new input layer, 465 resulting in each input qubit controlling an operation on each target qubit at some point in the network. 466 467 If False, only convolutional layers are added. In either case, convolutional layers alternate 468 between all-to-all and neighbour-to-neighbour layers. Default is True. 469 470 - **initial_IL** : *boolean* 471 472 If True, add an input layer at the beginning of the circuit. Default is True. 473 474 - **input_Ry** : *boolean* 475 476 If True, initially apply a sequence of parametrised Ry rotations to the input register. Default is False. 477 478 - **real** : *boolean* 479 480 If True, use the real versions of input and convolutional layers, which only contain CX and Ry gates and hence ensure 481 real amplitudes. Default is False. 482 483 - **inverse** : *boolean* 484 485 If True, invert the circuit and return the inverse of the network. 486 487 - **repeat_params** : *str*, *optional* 488 489 Keep parameters fixed for different layer types, i.e. use the same parameter values for each instance of a layer type. 490 Options are `'CL'` (keep parameters fixed for convolutional layers), `'IL'` (keep parameters fixed for input layers), `'both'` 491 (keep parameters fixed for both convolutional and input layers). 492 493 - **wrap**: *boolean* 494 495 If True, map rotation angles to an interval specified by `map_angle()`. Default is False. 496 497 498 Returns: 499 ---- 500 - **circuit** : *QuantumCircuit* 501 502 The qiskit `QuantumCircuit` implementation of the network, with unassigned parameters. 503 504 """ 505 # initialise parameter vector 506 if repeat_params==None: 507 AA_CL_params=None 508 NN_CL_params=None 509 IL_params=None 510 elif repeat_params=="CL": 511 IL_params=None 512 AA_CL_params=ParameterVector(u"\u03B8_CL_AA", length= int((3 if real==False else 2 ) * (0.5 * m * (m-1)))) 513 NN_CL_params=ParameterVector(u"\u03B8_CL_NN", length= int((3 if real==False else 2) * m)) 514 elif repeat_params=="IL": 515 IL_params=ParameterVector(u"\u03B8_IL", length= int((3 if real==False else 1) * n)) 516 AA_CL_params=None 517 NN_CL_params=None 518 elif repeat_params=="both": 519 AA_CL_params=ParameterVector(u"\u03B8_CL_AA", length= int((3 if real==False else 2 ) * (0.5 * m * (m-1)))) 520 NN_CL_params=ParameterVector(u"\u03B8_CL_NN", length= int((3 if real==False else 2) * m)) 521 IL_params=ParameterVector(u"\u03B8_IL", length= int((3 if real==False else 1) * n)) 522 else: 523 raise ValueError("Unrecognised option for 'repeat_params'. Should be None, 'CL', 'IL', or 'both'.") 524 525 # initialise empty input and target registers 526 input_register = QuantumRegister(n, "input") 527 target_register = QuantumRegister(m, "target") 528 circuit = QuantumCircuit(input_register, target_register) 529 530 # prepare registers 531 circuit.h(target_register) 532 if encode: 533 circuit.compose(digital_encoding(n), input_register, inplace=True) 534 535 if input_Ry: 536 input_Ry_params = ParameterVector("Input_Ry",n) 537 for i in np.arange(n): 538 circuit.ry(input_Ry_params[i], input_register[i]) 539 540 if initial_IL: 541 # apply input layer 542 circuit.compose(input_layer(n,m, u"\u03B8_IL_0", real=real, params=IL_params, wrap=wrap), circuit.qubits, inplace=True) 543 544 # apply convolutional layers (alternating between AA and NN) 545 # if toggle_IL is True, additional input layers are added after each NN 546 for i in np.arange(L): 547 548 if toggle_IL==False: 549 550 if i % 2 ==0: 551 circuit.compose(conv_layer_AA(m, u"\u03B8_CL_AA_{0}".format(i // 2), real=real, params=AA_CL_params,wrap=wrap), target_register, inplace=True) 552 elif i % 2 ==1: 553 circuit.compose(conv_layer_NN(m, u"\u03B8_CL_NN_{0}".format(i // 2),real=real, params=NN_CL_params, wrap=wrap), target_register, inplace=True) 554 555 if toggle_IL==True: 556 557 # difference between number of input layers and number of target qubits: 558 del_IL = m - (int(initial_IL)+ L //3) 559 560 if del_IL <= 0: 561 if i % 3 ==0: 562 circuit.compose(conv_layer_AA(m, u"\u03B8_CL_AA_{0}".format(i // 3),real=real,params=AA_CL_params, wrap=wrap), target_register, inplace=True) 563 elif i % 3 ==1: 564 circuit.compose(conv_layer_NN(m, u"\u03B8_CL_NN_{0}".format(i // 3),real=real,params=NN_CL_params, wrap=wrap), target_register, inplace=True) 565 elif i % 3 ==2: 566 circuit.compose(input_layer(n,m, u"\u03B8_IL_{0}".format(i // 3 +1),shift=(i//3)+1, ctrl_state=(i % 2),real=real,params=IL_params, wrap=wrap), circuit.qubits, inplace=True) 567 else: 568 if i % 3 ==0: 569 circuit.compose(conv_layer_AA(m, u"\u03B8_CL_AA_{0}".format(i // 3),real=real,params=AA_CL_params, wrap=wrap), target_register, inplace=True) 570 elif i % 3 ==1: 571 circuit.compose(conv_layer_NN(m, u"\u03B8_CL_NN_{0}".format(i // 3),real=real,params=NN_CL_params, wrap=wrap), target_register, inplace=True) 572 elif i % 3 ==2: 573 # padd with additional input layers 574 for j in np.arange(del_IL // (L //3) +2): 575 shift = (i // 3) +int(initial_IL) + j * (L //3) 576 if shift <= m-1: 577 circuit.compose(input_layer(n,m, u"\u03B8_IL_{0}".format(shift),shift=shift, ctrl_state=((shift-int(initial_IL)) % 2),real=real,params=IL_params, wrap=wrap), circuit.qubits, inplace=True) 578 579 if inverse: 580 circuit=circuit.inverse() 581 582 return circuit
Set up a QCNN consisting of input and convolutional layers acting on two distinct registers, the 'input register' and the 'target register'.
Input layers consist of single-qubit operations applied to the target register, controlled by the input register qubits. Convolutional layers
consist of two-qubit operations applied to the target register. See input_layer() for more information on input layers and conv_layer_AA(),
conv_layer_NN() for more information on convolutional layers.
The network was designed for the task of performing function evaluation, i.e. to implement an operator $\hat{Q}_\Psi$ such that $$ \hat{Q}_\Psi \ket{j}_i \ket{0}_t = \ket{j}_i \ket{\Psi(j)}_t,$$ for some function $\Psi$ with the subscripts $i$ and $t$ denoting the input and target registers, respectively. With some adaptation, the network structure could also be used for other applications.
Arguments:
n : int
Number of qubits in the input register.
m : int
Number of qubits in the target register.
L : int
Number of layers in the network. Note that
Ldoes not take into account the optional initial input layer
added byinitial_IL. Further,Ldoes not take into account the padding of the network with additional input layers to ensure the number of input layers is at least equal tom.encode : boolean
If True, apply an initial encoding circuit to the input register which can be used control the input states of the network. Default is False.
toggle_IL : boolean
If True, every third layer added is an input layer and additional input layers are added to ensure the number of input layers is at least equal to
m, with input layers alternating between control states 0 and 1. The input layershiftparameter is successively increased for each new input layer, resulting in each input qubit controlling an operation on each target qubit at some point in the network.If False, only convolutional layers are added. In either case, convolutional layers alternate between all-to-all and neighbour-to-neighbour layers. Default is True.
initial_IL : boolean
If True, add an input layer at the beginning of the circuit. Default is True.
input_Ry : boolean
If True, initially apply a sequence of parametrised Ry rotations to the input register. Default is False.
real : boolean
If True, use the real versions of input and convolutional layers, which only contain CX and Ry gates and hence ensure real amplitudes. Default is False.
inverse : boolean
If True, invert the circuit and return the inverse of the network.
repeat_params : str, optional
Keep parameters fixed for different layer types, i.e. use the same parameter values for each instance of a layer type. Options are
'CL'(keep parameters fixed for convolutional layers),'IL'(keep parameters fixed for input layers),'both'(keep parameters fixed for both convolutional and input layers).wrap: boolean
If True, map rotation angles to an interval specified by
map_angle(). Default is False.
Returns:
circuit : QuantumCircuit
The qiskit
QuantumCircuitimplementation of the network, with unassigned parameters.
584def A_generate_network(n,L, repeat_params=False, wrap=False): 585 r""" 586 Set up a network consisting of real convolutional layers acting on a single qubit register. 587 588 Layers alternate between all-to-all and neighbour-to-neighbour convolutional layers. For more 589 information see `conv_layer_AA()`, `conv_layer_NN()`. The convolutional layers are *real* in 590 the sense of involving only CX and Ry operations, ensuring real amplitudes. 591 592 The network was designed for the task of encoding a suitably normalised real function, $A(j)$, into the amplitudes of the 593 register, i.e. to implement an operator $\hat{U}_A$ such that 594 $$ \hat{U}_A \ket{j}= A(j) \ket{j}.$$ 595 With some adaptation, the network structure could also be used for other applications. 596 597 Arguments: 598 --- 599 - **n** : *int* 600 601 Number of qubits in the register. 602 603 - **L** : *int* 604 605 Number of layers in the network. 606 607 - **repeat_params** : *boolean* 608 609 If True, use the same parameter values for each layer type. Default is False. 610 611 - **wrap**: *boolean* 612 613 If True, map rotation angles to an interval specified by `map_angle()`. Default is False. 614 615 Returns: 616 --- 617 - **circuit** : *QuantumCircuit* 618 619 The qiskit `QuantumCircuit` implementation of the network, with unassigned parameters. 620 621 """ 622 623 # initialise parameter vector 624 if repeat_params: 625 AA_CL_params=ParameterVector(u"\u03B8_AA", length= int(2 * (0.5 * n * (n-1)))) 626 NN_CL_params=ParameterVector(u"\u03B8_NN", length= int(2 * n)) 627 else: 628 AA_CL_params = None 629 NN_CL_params = None 630 631 632 # initialise empty input register 633 register = QuantumRegister(n, "reg") 634 circuit = QuantumCircuit(register) 635 636 # prepare register 637 circuit.h(register) 638 639 # apply L convolutional layers (alternating between AA and NN) 640 for i in np.arange(L): 641 642 if i % 2 ==0: 643 circuit.compose(conv_layer_AA(n, u"\u03B8_R_AA_{0}".format(i // 2), real=True, params=AA_CL_params,wrap=wrap), register, inplace=True) 644 elif i % 2 ==1: 645 circuit.compose(conv_layer_NN(n, u"\u03B8_R_NN_{0}".format(i // 2), real=True, params=NN_CL_params,wrap=wrap), register, inplace=True) 646 647 return circuit
Set up a network consisting of real convolutional layers acting on a single qubit register.
Layers alternate between all-to-all and neighbour-to-neighbour convolutional layers. For more
information see conv_layer_AA(), conv_layer_NN(). The convolutional layers are real in
the sense of involving only CX and Ry operations, ensuring real amplitudes.
The network was designed for the task of encoding a suitably normalised real function, $A(j)$, into the amplitudes of the
register, i.e. to implement an operator $\hat{U}_A$ such that
$$ \hat{U}_A \ket{j}= A(j) \ket{j}.$$
With some adaptation, the network structure could also be used for other applications.
Arguments:
n : int
Number of qubits in the register.
L : int
Number of layers in the network.
repeat_params : boolean
If True, use the same parameter values for each layer type. Default is False.
wrap: boolean
If True, map rotation angles to an interval specified by
map_angle(). Default is False.
Returns:
circuit : QuantumCircuit
The qiskit
QuantumCircuitimplementation of the network, with unassigned parameters.
649def get_state_vec(circuit): 650 """ 651 Get statevector produced by a quantum circuit. 652 653 Uses the `qiskit_aer` backend. 654 655 Arguments: 656 ---- 657 - **circuit** : *QuantumCircuit* 658 659 The circuit to be evaluated. 660 661 Returns: 662 ---- 663 - **state_vector** : *array_like* 664 665 Array storing the complex amplitudes of the system to be in each of the 666 `2**circuit.num_qubits` basis states. 667 668 """ 669 670 # Transpile for simulator 671 simulator = StatevectorSimulator() 672 circuit = transpile(circuit, simulator) 673 674 # Run and get counts 675 result = simulator.run(circuit).result() 676 state_vector= result.get_statevector(circuit) 677 678 return np.asarray(state_vector)
Get statevector produced by a quantum circuit.
Uses the qiskit_aer backend.
Arguments:
circuit : QuantumCircuit
The circuit to be evaluated.
Returns:
state_vector : array_like
Array storing the complex amplitudes of the system to be in each of the
2**circuit.num_qubitsbasis states.
680def map_angle(theta): 681 r""" 682 Map a rotation angle to the interval $(0, \frac{\pi}{2})$. 683 684 An inverse tan function is used for the mapping. 685 686 Arguments: 687 ---- 688 - **theta** : *float* 689 690 Rotation angle. 691 692 Returns: 693 --- 694 - **theta_mapped** : *float* 695 696 Rotation angle mapped to the given interval. 697 698 """ 699 700 return np.arctan(theta) + np.pi /2
Map a rotation angle to the interval $(0, \frac{\pi}{2})$.
An inverse tan function is used for the mapping.
Arguments:
theta : float
Rotation angle.
Returns:
theta_mapped : float
Rotation angle mapped to the given interval.