hqm.circuits.flexiblecircuit
1from types import FunctionType 2from itertools import chain 3import pennylane as qml 4import numpy as np 5import sys 6 7sys.path += ['.', './utils/'] 8 9from .circuit import QuantumCircuit 10 11class FlexibleCircuit(QuantumCircuit): 12 ''' 13 This class implements a torch/keras quantum layer using the flexible circuit. 14 ''' 15 16 def __init__(self, config : dict, dev : qml.devices = None, encoding : str = 'angle') -> None: 17 ''' 18 FlexibleCircuit constructor. 19 20 ^ --> 21 | <-- 22 ___ ___ ___ 23 |0> ---| | --- | | --- | | --- M 24 |0> ---| E | --- | F | --- | U | --- M 25 |0> ---| . | --- | | --- | | --- M 26 |0> ---| . | --- | | --- | | --- M 27 |0> ---|___| --- |___| --- |___| --- M 28 29 Where E is the encoding layer, F is a fixed layer and U is a configurable 30 and repeating layer. The configuration can be changed via a dictionary. 31 For instance, for a 3 qubits, 2 layers and full measurement circuit: 32 33 config = { 34 'F' : [ 35 ['H', 'CNOT-1'], #Q0 36 ['H', 'CNOT-2'], #Q1 37 ['H', 'CNOT-0'] #Q2 38 ], 39 'U' : [ 40 2*['RY', 'CNOT-1', 'RY'], #Q0 41 2*['RY', 'CNOT-2', 'RY'], #Q1 42 2*['RY', 'CNOT-0', 'RY'] #Q2 43 ], 44 'M' : [True, True, True] 45 } 46 47 will result in 48 *===== F ====*======== U1 =========*======== U2 ==========*= M =* 49 ___ 50 |0> ---| | --- H - X ----- | - Ry - X ----- | - Ry - Ry - X ----- | - Ry - M0 51 |0> ---| E | --- H - | - X - | - Ry - | - X - | - Ry - Ry - | - X - | - Ry - M1 52 |0> ---|___| --- H ----- | - X - Ry ----- | - X - Ry - Ry ----- | - X - Ry - M2 53 54 55 Parameters: 56 ----------- 57 - n_qubits : int 58 number of qubits for the quantum circuit 59 - n_layers : int 60 number of layers for the U circuit 61 - config : dict 62 dictionary that configures F and U circuit 63 - dev : qml.device 64 PennyLane device on wich run quantum operations (dafault : None). When None it will be set 65 to 'default.qubit' 66 - encoding : str 67 string representing the type of input data encoding in quantum circuit, can be 'angle' or 'amplitude' 68 69 Returns: 70 -------- 71 Nothing, a RealAmplitudesCircuit object will be created. 72 ''' 73 super().__init__(n_qubits=np.shape(config['U'])[0], n_layers=1, dev=dev) 74 75 if encoding not in ['angle', 'amplitude']: raise(f"encoding can be angle or amplitude, found {encoding}") 76 if 'F' not in config.keys(): raise(f'Config does not contain configuration for circuit F component, found {config.keys()}') 77 if 'U' not in config.keys(): raise(f'Config does not contain configuration for circuit U component, found {config.keys()}') 78 if 'M' not in config.keys(): raise(f'Config does not contain configuration for circuit M component, found {config.keys()}') 79 80 self.config = config 81 self.encoding = encoding 82 self.n_qubits = np.shape(config['U'])[0] 83 self.weight_shape = {"weights": (self.__calculate_weights(config))} 84 self.circuit = self.circ(self.dev, self.n_qubits, self.config, self.encoding) 85 86 def __calculate_weights(self, config): 87 ''' 88 Calculates the numer of rotational gates to infer the weights shape. 89 90 Parameters: 91 ----------- 92 - config : dict 93 dictionary that configures F and U circuit 94 95 Returns: 96 -------- 97 ct : int 98 counts of R gates. 99 ''' 100 101 ct = 0 102 for el in list(chain(*config['F'])): 103 if 'R' in el: 104 ct += 1 105 106 for el in list(chain(*config['U'])): 107 if 'R' in el: 108 ct += 1 109 110 return ct 111 112 @staticmethod 113 def circ(dev : qml.devices, n_qubits : int, config: dict, encoding : str) -> FunctionType: 114 ''' 115 FlexibleCircuit static method that implements the quantum circuit. 116 117 Parameters: 118 ----------- 119 - dev : qml.device 120 PennyLane device on wich run quantum operations (dafault : None). When None it will be set 121 to 'default.qubit' 122 - n_qubits : int 123 number of qubits for the quantum circuit 124 - config : dict 125 dictionary that configures F and U circuit 126 - encoding : str 127 string representing the type of input data encoding in quantum circuit, can be 'angle' or 'amplitude' 128 129 Returns: 130 -------- 131 - qnode : qml.qnode 132 the actual PennyLane circuit 133 ''' 134 @qml.qnode(dev) 135 def qnode(inputs : np.ndarray, weights : np.ndarray) -> list: 136 ''' 137 PennyLane based quantum circuit composed of an angle embedding, fixed and configurable layers. 138 139 Parameters: 140 ----------- 141 - inputs : np.ndarray 142 array containing input values (can came from previous torch/keras layers or quantum layers) 143 - weights : np.ndarray 144 array containing the weights of the circuit that are tuned during training, the shape of this 145 array depends on circuit's layers and qubits. 146 147 Returns: 148 -------- 149 - measurements : list 150 list of values measured from the quantum circuits 151 ''' 152 153 # E component 154 if encoding == 'angle': qml.AngleEmbedding(inputs, wires=range(n_qubits)) 155 if encoding == 'amplitude': qml.AmplitudeEmbedding(features=inputs, wires=range(n_qubits), normalize=True) 156 157 ct = 0 158 # V component 159 V = config['F'] 160 for j in range(np.shape(V)[1]): 161 for i in range(np.shape(V)[0]): 162 ct = decode_gates(key=V[i][j], qubit=i, weights=weights, ct=ct) 163 164 # U Component 165 U = config['U'] 166 for j in range(np.shape(U)[1]): 167 for i in range(np.shape(U)[0]): 168 ct = decode_gates(key=U[i][j], qubit=i, weights=weights, ct=ct) 169 170 # M component 171 measurements = [] 172 for i in range(n_qubits): 173 if config['M'][i]: 174 measurements.append(qml.expval(qml.PauliZ(wires=i))) 175 176 return measurements 177 178 return qnode 179 180def decode_gates(key : str, qubit : int, weights : np.ndarray, ct : int): 181 ''' 182 Decode string into qml gate 183 184 Parameters: 185 ----------- 186 - key : str 187 string representing gate 188 - qubit : int 189 to which qubit apply the gate 190 - weights : np.ndarray 191 array containing the weights of the circuit that are tuned during training, the shape of this 192 array depends on circuit's layers and qubits. 193 - ct : int 194 counter that keeps track of weight position 195 196 Returns: 197 -------- 198 Nothing 199 ''' 200 201 if key == 'H': 202 qml.Hadamard(wires=qubit) 203 if key == 'RY': 204 qml.RY(weights[ct], wires=qubit) 205 ct += 1 206 if key == 'RX': 207 qml.RX(weights[ct], wires=qubit) 208 ct += 1 209 if key == 'RZ': 210 qml.RZ(weights[ct], wires=qubit) 211 ct += 1 212 if 'CNOT' in key: 213 qx = int(key.split('-')[-1]) 214 qml.CNOT(wires=[qubit, qx]) 215 216 return ct
12class FlexibleCircuit(QuantumCircuit): 13 ''' 14 This class implements a torch/keras quantum layer using the flexible circuit. 15 ''' 16 17 def __init__(self, config : dict, dev : qml.devices = None, encoding : str = 'angle') -> None: 18 ''' 19 FlexibleCircuit constructor. 20 21 ^ --> 22 | <-- 23 ___ ___ ___ 24 |0> ---| | --- | | --- | | --- M 25 |0> ---| E | --- | F | --- | U | --- M 26 |0> ---| . | --- | | --- | | --- M 27 |0> ---| . | --- | | --- | | --- M 28 |0> ---|___| --- |___| --- |___| --- M 29 30 Where E is the encoding layer, F is a fixed layer and U is a configurable 31 and repeating layer. The configuration can be changed via a dictionary. 32 For instance, for a 3 qubits, 2 layers and full measurement circuit: 33 34 config = { 35 'F' : [ 36 ['H', 'CNOT-1'], #Q0 37 ['H', 'CNOT-2'], #Q1 38 ['H', 'CNOT-0'] #Q2 39 ], 40 'U' : [ 41 2*['RY', 'CNOT-1', 'RY'], #Q0 42 2*['RY', 'CNOT-2', 'RY'], #Q1 43 2*['RY', 'CNOT-0', 'RY'] #Q2 44 ], 45 'M' : [True, True, True] 46 } 47 48 will result in 49 *===== F ====*======== U1 =========*======== U2 ==========*= M =* 50 ___ 51 |0> ---| | --- H - X ----- | - Ry - X ----- | - Ry - Ry - X ----- | - Ry - M0 52 |0> ---| E | --- H - | - X - | - Ry - | - X - | - Ry - Ry - | - X - | - Ry - M1 53 |0> ---|___| --- H ----- | - X - Ry ----- | - X - Ry - Ry ----- | - X - Ry - M2 54 55 56 Parameters: 57 ----------- 58 - n_qubits : int 59 number of qubits for the quantum circuit 60 - n_layers : int 61 number of layers for the U circuit 62 - config : dict 63 dictionary that configures F and U circuit 64 - dev : qml.device 65 PennyLane device on wich run quantum operations (dafault : None). When None it will be set 66 to 'default.qubit' 67 - encoding : str 68 string representing the type of input data encoding in quantum circuit, can be 'angle' or 'amplitude' 69 70 Returns: 71 -------- 72 Nothing, a RealAmplitudesCircuit object will be created. 73 ''' 74 super().__init__(n_qubits=np.shape(config['U'])[0], n_layers=1, dev=dev) 75 76 if encoding not in ['angle', 'amplitude']: raise(f"encoding can be angle or amplitude, found {encoding}") 77 if 'F' not in config.keys(): raise(f'Config does not contain configuration for circuit F component, found {config.keys()}') 78 if 'U' not in config.keys(): raise(f'Config does not contain configuration for circuit U component, found {config.keys()}') 79 if 'M' not in config.keys(): raise(f'Config does not contain configuration for circuit M component, found {config.keys()}') 80 81 self.config = config 82 self.encoding = encoding 83 self.n_qubits = np.shape(config['U'])[0] 84 self.weight_shape = {"weights": (self.__calculate_weights(config))} 85 self.circuit = self.circ(self.dev, self.n_qubits, self.config, self.encoding) 86 87 def __calculate_weights(self, config): 88 ''' 89 Calculates the numer of rotational gates to infer the weights shape. 90 91 Parameters: 92 ----------- 93 - config : dict 94 dictionary that configures F and U circuit 95 96 Returns: 97 -------- 98 ct : int 99 counts of R gates. 100 ''' 101 102 ct = 0 103 for el in list(chain(*config['F'])): 104 if 'R' in el: 105 ct += 1 106 107 for el in list(chain(*config['U'])): 108 if 'R' in el: 109 ct += 1 110 111 return ct 112 113 @staticmethod 114 def circ(dev : qml.devices, n_qubits : int, config: dict, encoding : str) -> FunctionType: 115 ''' 116 FlexibleCircuit static method that implements the quantum circuit. 117 118 Parameters: 119 ----------- 120 - dev : qml.device 121 PennyLane device on wich run quantum operations (dafault : None). When None it will be set 122 to 'default.qubit' 123 - n_qubits : int 124 number of qubits for the quantum circuit 125 - config : dict 126 dictionary that configures F and U circuit 127 - encoding : str 128 string representing the type of input data encoding in quantum circuit, can be 'angle' or 'amplitude' 129 130 Returns: 131 -------- 132 - qnode : qml.qnode 133 the actual PennyLane circuit 134 ''' 135 @qml.qnode(dev) 136 def qnode(inputs : np.ndarray, weights : np.ndarray) -> list: 137 ''' 138 PennyLane based quantum circuit composed of an angle embedding, fixed and configurable layers. 139 140 Parameters: 141 ----------- 142 - inputs : np.ndarray 143 array containing input values (can came from previous torch/keras layers or quantum layers) 144 - weights : np.ndarray 145 array containing the weights of the circuit that are tuned during training, the shape of this 146 array depends on circuit's layers and qubits. 147 148 Returns: 149 -------- 150 - measurements : list 151 list of values measured from the quantum circuits 152 ''' 153 154 # E component 155 if encoding == 'angle': qml.AngleEmbedding(inputs, wires=range(n_qubits)) 156 if encoding == 'amplitude': qml.AmplitudeEmbedding(features=inputs, wires=range(n_qubits), normalize=True) 157 158 ct = 0 159 # V component 160 V = config['F'] 161 for j in range(np.shape(V)[1]): 162 for i in range(np.shape(V)[0]): 163 ct = decode_gates(key=V[i][j], qubit=i, weights=weights, ct=ct) 164 165 # U Component 166 U = config['U'] 167 for j in range(np.shape(U)[1]): 168 for i in range(np.shape(U)[0]): 169 ct = decode_gates(key=U[i][j], qubit=i, weights=weights, ct=ct) 170 171 # M component 172 measurements = [] 173 for i in range(n_qubits): 174 if config['M'][i]: 175 measurements.append(qml.expval(qml.PauliZ(wires=i))) 176 177 return measurements 178 179 return qnode
This class implements a torch/keras quantum layer using the flexible circuit.
17 def __init__(self, config : dict, dev : qml.devices = None, encoding : str = 'angle') -> None: 18 ''' 19 FlexibleCircuit constructor. 20 21 ^ --> 22 | <-- 23 ___ ___ ___ 24 |0> ---| | --- | | --- | | --- M 25 |0> ---| E | --- | F | --- | U | --- M 26 |0> ---| . | --- | | --- | | --- M 27 |0> ---| . | --- | | --- | | --- M 28 |0> ---|___| --- |___| --- |___| --- M 29 30 Where E is the encoding layer, F is a fixed layer and U is a configurable 31 and repeating layer. The configuration can be changed via a dictionary. 32 For instance, for a 3 qubits, 2 layers and full measurement circuit: 33 34 config = { 35 'F' : [ 36 ['H', 'CNOT-1'], #Q0 37 ['H', 'CNOT-2'], #Q1 38 ['H', 'CNOT-0'] #Q2 39 ], 40 'U' : [ 41 2*['RY', 'CNOT-1', 'RY'], #Q0 42 2*['RY', 'CNOT-2', 'RY'], #Q1 43 2*['RY', 'CNOT-0', 'RY'] #Q2 44 ], 45 'M' : [True, True, True] 46 } 47 48 will result in 49 *===== F ====*======== U1 =========*======== U2 ==========*= M =* 50 ___ 51 |0> ---| | --- H - X ----- | - Ry - X ----- | - Ry - Ry - X ----- | - Ry - M0 52 |0> ---| E | --- H - | - X - | - Ry - | - X - | - Ry - Ry - | - X - | - Ry - M1 53 |0> ---|___| --- H ----- | - X - Ry ----- | - X - Ry - Ry ----- | - X - Ry - M2 54 55 56 Parameters: 57 ----------- 58 - n_qubits : int 59 number of qubits for the quantum circuit 60 - n_layers : int 61 number of layers for the U circuit 62 - config : dict 63 dictionary that configures F and U circuit 64 - dev : qml.device 65 PennyLane device on wich run quantum operations (dafault : None). When None it will be set 66 to 'default.qubit' 67 - encoding : str 68 string representing the type of input data encoding in quantum circuit, can be 'angle' or 'amplitude' 69 70 Returns: 71 -------- 72 Nothing, a RealAmplitudesCircuit object will be created. 73 ''' 74 super().__init__(n_qubits=np.shape(config['U'])[0], n_layers=1, dev=dev) 75 76 if encoding not in ['angle', 'amplitude']: raise(f"encoding can be angle or amplitude, found {encoding}") 77 if 'F' not in config.keys(): raise(f'Config does not contain configuration for circuit F component, found {config.keys()}') 78 if 'U' not in config.keys(): raise(f'Config does not contain configuration for circuit U component, found {config.keys()}') 79 if 'M' not in config.keys(): raise(f'Config does not contain configuration for circuit M component, found {config.keys()}') 80 81 self.config = config 82 self.encoding = encoding 83 self.n_qubits = np.shape(config['U'])[0] 84 self.weight_shape = {"weights": (self.__calculate_weights(config))} 85 self.circuit = self.circ(self.dev, self.n_qubits, self.config, self.encoding)
FlexibleCircuit constructor.
^ -->
| <--
___ ___ ___
|0> ---| | --- | | --- | | --- M
|0> ---| E | --- | F | --- | U | --- M
|0> ---| . | --- | | --- | | --- M
|0> ---| . | --- | | --- | | --- M
|0> ---|___| --- |___| --- |___| --- M
Where E is the encoding layer, F is a fixed layer and U is a configurable and repeating layer. The configuration can be changed via a dictionary. For instance, for a 3 qubits, 2 layers and full measurement circuit:
config = {
'F' : [
['H', 'CNOT-1'], #Q0
['H', 'CNOT-2'], #Q1
['H', 'CNOT-0'] #Q2
],
'U' : [
2['RY', 'CNOT-1', 'RY'], #Q0
2['RY', 'CNOT-2', 'RY'], #Q1
2*['RY', 'CNOT-0', 'RY'] #Q2
],
'M' : [True, True, True]
}
will result in
===== F ============ U1 ================= U2 =========== M =*
___
|0> ---| | --- H - X ----- | - Ry - X ----- | - Ry - Ry - X ----- | - Ry - M0
|0> ---| E | --- H - | - X - | - Ry - | - X - | - Ry - Ry - | - X - | - Ry - M1
|0> ---|___| --- H ----- | - X - Ry ----- | - X - Ry - Ry ----- | - X - Ry - M2
Parameters:
- n_qubits : int
number of qubits for the quantum circuit - n_layers : int
number of layers for the U circuit - config : dict dictionary that configures F and U circuit
- dev : qml.device
PennyLane device on wich run quantum operations (dafault : None). When None it will be set to 'default.qubit' - encoding : str
string representing the type of input data encoding in quantum circuit, can be 'angle' or 'amplitude'
Returns:
Nothing, a RealAmplitudesCircuit object will be created.
113 @staticmethod 114 def circ(dev : qml.devices, n_qubits : int, config: dict, encoding : str) -> FunctionType: 115 ''' 116 FlexibleCircuit static method that implements the quantum circuit. 117 118 Parameters: 119 ----------- 120 - dev : qml.device 121 PennyLane device on wich run quantum operations (dafault : None). When None it will be set 122 to 'default.qubit' 123 - n_qubits : int 124 number of qubits for the quantum circuit 125 - config : dict 126 dictionary that configures F and U circuit 127 - encoding : str 128 string representing the type of input data encoding in quantum circuit, can be 'angle' or 'amplitude' 129 130 Returns: 131 -------- 132 - qnode : qml.qnode 133 the actual PennyLane circuit 134 ''' 135 @qml.qnode(dev) 136 def qnode(inputs : np.ndarray, weights : np.ndarray) -> list: 137 ''' 138 PennyLane based quantum circuit composed of an angle embedding, fixed and configurable layers. 139 140 Parameters: 141 ----------- 142 - inputs : np.ndarray 143 array containing input values (can came from previous torch/keras layers or quantum layers) 144 - weights : np.ndarray 145 array containing the weights of the circuit that are tuned during training, the shape of this 146 array depends on circuit's layers and qubits. 147 148 Returns: 149 -------- 150 - measurements : list 151 list of values measured from the quantum circuits 152 ''' 153 154 # E component 155 if encoding == 'angle': qml.AngleEmbedding(inputs, wires=range(n_qubits)) 156 if encoding == 'amplitude': qml.AmplitudeEmbedding(features=inputs, wires=range(n_qubits), normalize=True) 157 158 ct = 0 159 # V component 160 V = config['F'] 161 for j in range(np.shape(V)[1]): 162 for i in range(np.shape(V)[0]): 163 ct = decode_gates(key=V[i][j], qubit=i, weights=weights, ct=ct) 164 165 # U Component 166 U = config['U'] 167 for j in range(np.shape(U)[1]): 168 for i in range(np.shape(U)[0]): 169 ct = decode_gates(key=U[i][j], qubit=i, weights=weights, ct=ct) 170 171 # M component 172 measurements = [] 173 for i in range(n_qubits): 174 if config['M'][i]: 175 measurements.append(qml.expval(qml.PauliZ(wires=i))) 176 177 return measurements 178 179 return qnode
FlexibleCircuit static method that implements the quantum circuit.
Parameters:
- dev : qml.device
PennyLane device on wich run quantum operations (dafault : None). When None it will be set
to 'default.qubit' - n_qubits : int
number of qubits for the quantum circuit - config : dict dictionary that configures F and U circuit
- encoding : str
string representing the type of input data encoding in quantum circuit, can be 'angle' or 'amplitude'
Returns:
- qnode : qml.qnode
the actual PennyLane circuit
181def decode_gates(key : str, qubit : int, weights : np.ndarray, ct : int): 182 ''' 183 Decode string into qml gate 184 185 Parameters: 186 ----------- 187 - key : str 188 string representing gate 189 - qubit : int 190 to which qubit apply the gate 191 - weights : np.ndarray 192 array containing the weights of the circuit that are tuned during training, the shape of this 193 array depends on circuit's layers and qubits. 194 - ct : int 195 counter that keeps track of weight position 196 197 Returns: 198 -------- 199 Nothing 200 ''' 201 202 if key == 'H': 203 qml.Hadamard(wires=qubit) 204 if key == 'RY': 205 qml.RY(weights[ct], wires=qubit) 206 ct += 1 207 if key == 'RX': 208 qml.RX(weights[ct], wires=qubit) 209 ct += 1 210 if key == 'RZ': 211 qml.RZ(weights[ct], wires=qubit) 212 ct += 1 213 if 'CNOT' in key: 214 qx = int(key.split('-')[-1]) 215 qml.CNOT(wires=[qubit, qx]) 216 217 return ct
Decode string into qml gate
Parameters:
-----------
- key : str
string representing gate
- qubit : int
to which qubit apply the gate
- weights : np.ndarray
array containing the weights of the circuit that are tuned during training, the shape of this
array depends on circuit's layers and qubits.
- ct : int
counter that keeps track of weight position
Returns:
--------
Nothing