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
class FlexibleCircuit(typing.Generic[~quantumcircuit]):
 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.

FlexibleCircuit( config: dict, dev: <module 'pennylane.devices' from '/opt/miniconda3/envs/hqm/lib/python3.10/site-packages/pennylane/devices/__init__.py'> = None, encoding: str = 'angle')
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.

@staticmethod
def circ( dev: <module 'pennylane.devices' from '/opt/miniconda3/envs/hqm/lib/python3.10/site-packages/pennylane/devices/__init__.py'>, n_qubits: int, config: dict, encoding: str) -> function:
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
def decode_gates(key: str, qubit: int, weights: numpy.ndarray, ct: int):
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