@@ -87,7 +87,7 @@ def simulate(circuit: QuantumScript, state: LightningStateVector, mcmc: dict = N
87
87
return LightningMeasurements (final_state , ** mcmc ).measure_final_state (circuit )
88
88
89
89
90
- def jacobian (circuit : QuantumTape , state : LightningStateVector , batch_obs = False ):
90
+ def jacobian (circuit : QuantumTape , state : LightningStateVector , batch_obs = False , wire_map = None ):
91
91
"""Compute the Jacobian for a single quantum script.
92
92
93
93
Args:
@@ -96,17 +96,21 @@ def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False)
96
96
batch_obs (bool): Determine whether we process observables in parallel when
97
97
computing the jacobian. This value is only relevant when the lightning
98
98
qubit is built with OpenMP. Default is False.
99
+ wire_map (Optional[dict]): a map from wire labels to simulation indices
99
100
100
101
Returns:
101
102
TensorLike: The Jacobian of the quantum script
102
103
"""
103
- circuit = circuit .map_to_standard_wires ()
104
+ if wire_map is not None :
105
+ [circuit ], _ = qml .map_wires (circuit , wire_map )
104
106
state .reset_state ()
105
107
final_state = state .get_final_state (circuit )
106
108
return LightningAdjointJacobian (final_state , batch_obs = batch_obs ).calculate_jacobian (circuit )
107
109
108
110
109
- def simulate_and_jacobian (circuit : QuantumTape , state : LightningStateVector , batch_obs = False ):
111
+ def simulate_and_jacobian (
112
+ circuit : QuantumTape , state : LightningStateVector , batch_obs = False , wire_map = None
113
+ ):
110
114
"""Simulate a single quantum script and compute its Jacobian.
111
115
112
116
Args:
@@ -115,20 +119,26 @@ def simulate_and_jacobian(circuit: QuantumTape, state: LightningStateVector, bat
115
119
batch_obs (bool): Determine whether we process observables in parallel when
116
120
computing the jacobian. This value is only relevant when the lightning
117
121
qubit is built with OpenMP. Default is False.
122
+ wire_map (Optional[dict]): a map from wire labels to simulation indices
118
123
119
124
Returns:
120
125
Tuple[TensorLike]: The results of the simulation and the calculated Jacobian
121
126
122
127
Note that this function can return measurements for non-commuting observables simultaneously.
123
128
"""
124
- circuit = circuit .map_to_standard_wires ()
129
+ if wire_map is not None :
130
+ [circuit ], _ = qml .map_wires (circuit , wire_map )
125
131
res = simulate (circuit , state )
126
132
jac = LightningAdjointJacobian (state , batch_obs = batch_obs ).calculate_jacobian (circuit )
127
133
return res , jac
128
134
129
135
130
136
def vjp (
131
- circuit : QuantumTape , cotangents : Tuple [Number ], state : LightningStateVector , batch_obs = False
137
+ circuit : QuantumTape ,
138
+ cotangents : Tuple [Number ],
139
+ state : LightningStateVector ,
140
+ batch_obs = False ,
141
+ wire_map = None ,
132
142
):
133
143
"""Compute the Vector-Jacobian Product (VJP) for a single quantum script.
134
144
Args:
@@ -141,10 +151,13 @@ def vjp(
141
151
batch_obs (bool): Determine whether we process observables in parallel when
142
152
computing the VJP. This value is only relevant when the lightning
143
153
qubit is built with OpenMP.
154
+ wire_map (Optional[dict]): a map from wire labels to simulation indices
155
+
144
156
Returns:
145
157
TensorLike: The VJP of the quantum script
146
158
"""
147
- circuit = circuit .map_to_standard_wires ()
159
+ if wire_map is not None :
160
+ [circuit ], _ = qml .map_wires (circuit , wire_map )
148
161
state .reset_state ()
149
162
final_state = state .get_final_state (circuit )
150
163
return LightningAdjointJacobian (final_state , batch_obs = batch_obs ).calculate_vjp (
@@ -153,7 +166,11 @@ def vjp(
153
166
154
167
155
168
def simulate_and_vjp (
156
- circuit : QuantumTape , cotangents : Tuple [Number ], state : LightningStateVector , batch_obs = False
169
+ circuit : QuantumTape ,
170
+ cotangents : Tuple [Number ],
171
+ state : LightningStateVector ,
172
+ batch_obs = False ,
173
+ wire_map = None ,
157
174
):
158
175
"""Simulate a single quantum script and compute its Vector-Jacobian Product (VJP).
159
176
Args:
@@ -166,11 +183,14 @@ def simulate_and_vjp(
166
183
batch_obs (bool): Determine whether we process observables in parallel when
167
184
computing the jacobian. This value is only relevant when the lightning
168
185
qubit is built with OpenMP.
186
+ wire_map (Optional[dict]): a map from wire labels to simulation indices
187
+
169
188
Returns:
170
189
Tuple[TensorLike]: The results of the simulation and the calculated VJP
171
190
Note that this function can return measurements for non-commuting observables simultaneously.
172
191
"""
173
- circuit = circuit .map_to_standard_wires ()
192
+ if wire_map is not None :
193
+ [circuit ], _ = qml .map_wires (circuit , wire_map )
174
194
res = simulate (circuit , state )
175
195
_vjp = LightningAdjointJacobian (state , batch_obs = batch_obs ).calculate_vjp (circuit , cotangents )
176
196
return res , _vjp
@@ -449,6 +469,11 @@ def __init__( # pylint: disable=too-many-arguments
449
469
450
470
super ().__init__ (wires = wires , shots = shots )
451
471
472
+ if isinstance (wires , int ):
473
+ self ._wire_map = None # should just use wires as is
474
+ else :
475
+ self ._wire_map = {w : i for i , w in enumerate (self .wires )}
476
+
452
477
self ._statevector = LightningStateVector (num_wires = len (self .wires ), dtype = c_dtype )
453
478
454
479
# TODO: Investigate usefulness of creating numpy random generator
@@ -568,7 +593,8 @@ def execute(
568
593
}
569
594
results = []
570
595
for circuit in circuits :
571
- circuit = circuit .map_to_standard_wires ()
596
+ if self ._wire_map is not None :
597
+ [circuit ], _ = qml .map_wires (circuit , self ._wire_map )
572
598
results .append (simulate (circuit , self ._statevector , mcmc = mcmc ))
573
599
574
600
return tuple (results )
@@ -613,8 +639,10 @@ def compute_derivatives(
613
639
Tuple: The jacobian for each trainable parameter
614
640
"""
615
641
batch_obs = execution_config .device_options .get ("batch_obs" , self ._batch_obs )
642
+
616
643
return tuple (
617
- jacobian (circuit , self ._statevector , batch_obs = batch_obs ) for circuit in circuits
644
+ jacobian (circuit , self ._statevector , batch_obs = batch_obs , wire_map = self ._wire_map )
645
+ for circuit in circuits
618
646
)
619
647
620
648
def execute_and_compute_derivatives (
@@ -633,7 +661,10 @@ def execute_and_compute_derivatives(
633
661
"""
634
662
batch_obs = execution_config .device_options .get ("batch_obs" , self ._batch_obs )
635
663
results = tuple (
636
- simulate_and_jacobian (c , self ._statevector , batch_obs = batch_obs ) for c in circuits
664
+ simulate_and_jacobian (
665
+ c , self ._statevector , batch_obs = batch_obs , wire_map = self ._wire_map
666
+ )
667
+ for c in circuits
637
668
)
638
669
return tuple (zip (* results ))
639
670
@@ -686,7 +717,7 @@ def compute_vjp(
686
717
"""
687
718
batch_obs = execution_config .device_options .get ("batch_obs" , self ._batch_obs )
688
719
return tuple (
689
- vjp (circuit , cots , self ._statevector , batch_obs = batch_obs )
720
+ vjp (circuit , cots , self ._statevector , batch_obs = batch_obs , wire_map = self . _wire_map )
690
721
for circuit , cots in zip (circuits , cotangents )
691
722
)
692
723
@@ -708,7 +739,9 @@ def execute_and_compute_vjp(
708
739
"""
709
740
batch_obs = execution_config .device_options .get ("batch_obs" , self ._batch_obs )
710
741
results = tuple (
711
- simulate_and_vjp (circuit , cots , self ._statevector , batch_obs = batch_obs )
742
+ simulate_and_vjp (
743
+ circuit , cots , self ._statevector , batch_obs = batch_obs , wire_map = self ._wire_map
744
+ )
712
745
for circuit , cots in zip (circuits , cotangents )
713
746
)
714
747
return tuple (zip (* results ))
0 commit comments