@@ -130,14 +130,15 @@ def __init__(
130
130
):
131
131
if data is None :
132
132
data = []
133
- self .nb_cbits = nb_cbits
134
- """See parameter description."""
135
133
self .label = label
136
134
"""See parameter description."""
137
135
self .instructions : list [Instruction ] = []
138
136
"""List of instructions of the circuit."""
139
137
self .noises : list [NoiseModel ] = []
140
138
"""List of noise models attached to the circuit."""
139
+ self ._user_nb_cbits : Optional [int ] = None
140
+ self ._nb_cbits : int
141
+
141
142
self ._user_nb_qubits : Optional [int ] = None
142
143
self ._nb_qubits : int
143
144
@@ -146,6 +147,10 @@ def __init__(
146
147
to OpenQASM2. It is used to correct the global phase when the job type
147
148
is STATE_VECTOR, and when this circuit contains CustomGate."""
148
149
150
+ if nb_cbits is None :
151
+ self ._nb_cbits = 0
152
+ else :
153
+ self ._user_nb_cbits = nb_cbits
149
154
if isinstance (data , int ):
150
155
if data < 0 :
151
156
raise TypeCheckError (
@@ -161,7 +166,7 @@ def __init__(
161
166
connections : set [int ] = set .union (
162
167
* (instruction .connections () for instruction in data )
163
168
)
164
- self ._nb_qubits = max (connections ) + 1
169
+ self ._nb_qubits = max (connections , default = 0 ) + 1
165
170
else :
166
171
self ._user_nb_qubits = nb_qubits
167
172
self .add (deepcopy (data ))
@@ -235,13 +240,11 @@ def add(self, components: OneOrMany[Instruction | NoiseModel]):
235
240
236
241
self ._check_components_targets (components )
237
242
if isinstance (components , BasisMeasure ):
238
- if self .nb_cbits is None :
239
- self .nb_cbits = 0
240
243
if components .c_targets is None :
241
244
components .c_targets = [
242
245
self .nb_cbits + i for i in range (len (components .targets ))
243
246
]
244
- self .nb_cbits = max ( self . nb_cbits , max (components .c_targets ) + 1 )
247
+ self ._update_cbits ( max (components .c_targets ) + 1 )
245
248
246
249
if isinstance (components , NoiseModel ):
247
250
self .noises .append (components )
@@ -273,6 +276,15 @@ def _check_components_targets(self, components: Instruction | NoiseModel):
273
276
"In noisy circuits, BasisMeasure must span all qubits in the circuit."
274
277
)
275
278
279
+ def _update_cbits (self , cbits : int ):
280
+ if self ._user_nb_cbits is not None :
281
+ if cbits > self ._user_nb_cbits :
282
+ raise ValueError (
283
+ f"nb_cbits in the circuit is static ({ self ._user_nb_cbits } ), but the nb_cbits of the components overflow ({ cbits } )."
284
+ )
285
+ else :
286
+ self ._nb_cbits = max (self .nb_cbits , cbits )
287
+
276
288
def _update_targets_components (self , component : Instruction | NoiseModel ):
277
289
"""Update the targets of the component with the number of qubits in the circuit.
278
290
@@ -326,8 +338,6 @@ def _update_targets_components(self, component: Instruction | NoiseModel):
326
338
327
339
component .basis .set_size (self .nb_qubits )
328
340
329
- if self .nb_cbits is None :
330
- self .nb_cbits = 0
331
341
unique_cbits = set ()
332
342
for instruction in self .instructions :
333
343
if instruction != component and isinstance (instruction , BasisMeasure ):
@@ -345,23 +355,44 @@ def _update_targets_components(self, component: Instruction | NoiseModel):
345
355
c_targets .append (i )
346
356
i += 1
347
357
component .c_targets = c_targets
348
- self .nb_cbits = max (
349
- max (c_targets , default = 0 ) + 1 , max (unique_cbits , default = 0 ) + 1
350
- )
351
358
359
+ self ._update_cbits (
360
+ max (max (c_targets , default = 0 ) + 1 , max (unique_cbits , default = 0 ) + 1 )
361
+ )
352
362
return component
353
363
354
364
@property
355
365
def nb_qubits (self ) -> int :
356
366
"""Number of qubits of the circuit."""
357
367
return self ._nb_qubits if self ._user_nb_qubits is None else self ._user_nb_qubits
358
368
369
+ @property
370
+ def nb_cbits (self ) -> int :
371
+ """Number of cbits of the circuit."""
372
+ return self ._nb_cbits if self ._user_nb_cbits is None else self ._user_nb_cbits
373
+
359
374
@nb_qubits .setter
360
375
def nb_qubits (self , nb_qubits : int ):
361
376
if self ._user_nb_qubits is None or self ._user_nb_qubits != nb_qubits :
362
377
self ._user_nb_qubits = nb_qubits
363
378
self ._set_nb_qubits_dynamic (nb_qubits )
364
379
380
+ @nb_cbits .setter
381
+ def nb_cbits (self , nb_cbits : int ):
382
+ if self ._user_nb_cbits is None or self ._user_nb_cbits != nb_cbits :
383
+ for measure in self .measurements :
384
+ if (
385
+ isinstance (measure , BasisMeasure )
386
+ and measure .c_targets is not None
387
+ and any (target <= nb_cbits for target in measure .c_targets )
388
+ ):
389
+ raise ValueError (
390
+ f"Targets of the measure { repr (measure )} are not "
391
+ "compatible with the classical bits register size "
392
+ f"requested { nb_cbits } ."
393
+ )
394
+ self ._user_nb_cbits = nb_cbits
395
+
365
396
def _set_nb_qubits_dynamic (self , nb_qubits : int ):
366
397
if not hasattr (self , "_nb_qubits" ) or nb_qubits != self ._nb_qubits :
367
398
self ._nb_qubits = nb_qubits
@@ -905,7 +936,7 @@ def without_measurements(self) -> QCircuit:
905
936
906
937
"""
907
938
new_circuit = deepcopy (self )
908
- new_circuit .nb_cbits = 0
939
+ new_circuit ._nb_cbits = 0
909
940
new_circuit .instructions = [
910
941
inst for inst in self .instructions if not isinstance (inst , Measure )
911
942
]
@@ -956,15 +987,36 @@ def without_noises(self) -> QCircuit:
956
987
new_circuit .noises = []
957
988
return new_circuit
958
989
990
+ def pre_measure (self ) -> QCircuit :
991
+ circuit = QCircuit ()
992
+ circuit ._set_nb_qubits_dynamic (self .nb_qubits )
993
+ for measure in self .measurements :
994
+ if isinstance (measure , BasisMeasure ):
995
+ if len (measure .pre_measure .instructions ) != 0 :
996
+ circuit .add (Barrier ())
997
+ circuit = circuit + measure .pre_measure
998
+ if isinstance (measure , ExpectationMeasure ):
999
+ if len (measure .pre_measure .instructions ) != 0 :
1000
+ circuit .add (Barrier ())
1001
+ circuit = circuit + measure .pre_measure
1002
+ return circuit
1003
+
959
1004
def to_other_language (
960
1005
self ,
961
1006
language : Language = Language .QISKIT ,
962
1007
cirq_proc_id : Optional [str ] = None ,
963
1008
translation_warning : bool = True ,
1009
+ skip_pre_measure : bool = False ,
964
1010
) -> QuantumCircuit | myQLM_Circuit | braket_Circuit | cirq_Circuit | str :
965
1011
"""Transforms this circuit into the corresponding circuit in the language
966
1012
specified in the ``language`` arg.
967
1013
1014
+ Some measurements require some adaptation between the user defined
1015
+ circuit and the measure. For instance if the targets are not given in a
1016
+ contiguous ordered list or if the basis measurement is in a basis other
1017
+ than the computational basis. We automatically add this adaptation as an
1018
+ intermediate circuit called ``pre_measure``.
1019
+
968
1020
By default, the circuit is translated to the corresponding
969
1021
``QuantumCircuit`` in Qiskit since this is the interface we use to
970
1022
generate the OpenQASM code.
@@ -975,7 +1027,9 @@ def to_other_language(
975
1027
976
1028
Args:
977
1029
language: Enum representing the target language.
978
- cirq_proc_id : Identifier of the processor for cirq.
1030
+ cirq_proc_id: Identifier of the processor for cirq.
1031
+ skip_pre_measure: If true, the ``pre_measure`` circuit will not be
1032
+ added to the output.
979
1033
980
1034
Returns:
981
1035
The corresponding circuit in the target language.
@@ -1021,7 +1075,15 @@ def to_other_language(
1021
1075
circuits.
1022
1076
1023
1077
"""
1024
-
1078
+ if not skip_pre_measure :
1079
+ circuit = self .without_measurements ()
1080
+ circuit += self .pre_measure ()
1081
+ circuit .add (self .measurements )
1082
+ circuit_other = circuit .to_other_language (
1083
+ language , cirq_proc_id , translation_warning , True
1084
+ )
1085
+ self .gphase = circuit .gphase
1086
+ return circuit_other
1025
1087
if language == Language .QISKIT :
1026
1088
from qiskit .circuit import Operation , QuantumCircuit
1027
1089
from qiskit .circuit .quantumcircuit import CircuitInstruction
@@ -1031,7 +1093,7 @@ def to_other_language(
1031
1093
# added parameters, and we use those instead of new ones when they
1032
1094
# are used more than once
1033
1095
qiskit_parameters = set ()
1034
- if self .nb_cbits is None :
1096
+ if self .nb_cbits == 0 :
1035
1097
new_circ = QuantumCircuit (self .nb_qubits )
1036
1098
else :
1037
1099
new_circ = QuantumCircuit (self .nb_qubits , self .nb_cbits )
@@ -1103,7 +1165,9 @@ def to_other_language(
1103
1165
1104
1166
elif language == Language .MY_QLM :
1105
1167
cleaned_circuit = self .without_measurements ()
1106
- qasm2_code = cleaned_circuit .to_other_language (Language .QASM2 )
1168
+ qasm2_code = cleaned_circuit .to_other_language (
1169
+ Language .QASM2 , skip_pre_measure = True
1170
+ )
1107
1171
self .gphase = cleaned_circuit .gphase
1108
1172
if TYPE_CHECKING :
1109
1173
assert isinstance (qasm2_code , str )
@@ -1140,7 +1204,7 @@ def to_other_language(
1140
1204
)
1141
1205
1142
1206
qasm3_code = circuit .to_other_language (
1143
- Language .QASM3 , translation_warning = False
1207
+ Language .QASM3 , translation_warning = False , skip_pre_measure = True
1144
1208
)
1145
1209
self .gphase = circuit .gphase
1146
1210
if TYPE_CHECKING :
@@ -1153,12 +1217,48 @@ def to_other_language(
1153
1217
self .nb_qubits ,
1154
1218
)
1155
1219
elif language == Language .CIRQ :
1156
- qasm2_code = self .to_other_language (Language .QASM2 )
1157
- if TYPE_CHECKING :
1158
- assert isinstance (qasm2_code , str )
1159
- from mpqp .qasm .qasm_to_cirq import qasm2_to_cirq_Circuit
1220
+ from cirq .circuits .circuit import Circuit as CirqCircuit
1221
+ from cirq .ops .identity import I
1222
+ from cirq .ops .named_qubit import NamedQubit
1223
+
1224
+ cirq_qubits = [NamedQubit (f"q_{ i } " ) for i in range (self .nb_qubits )]
1225
+ cirq_circuit = CirqCircuit ()
1226
+
1227
+ for qubit in cirq_qubits :
1228
+ cirq_circuit .append (I (qubit ))
1229
+
1230
+ for instruction in self .instructions :
1231
+ if isinstance (instruction , (ExpectationMeasure , Barrier , Breakpoint )):
1232
+ continue
1233
+ elif isinstance (instruction , CustomGate ):
1234
+ custom_circuit = QCircuit (self .nb_qubits )
1235
+ custom_circuit .add (instruction )
1236
+ qasm2_code = custom_circuit .to_other_language (
1237
+ Language .QASM2 , skip_pre_measure = True
1238
+ )
1239
+ if TYPE_CHECKING :
1240
+ assert isinstance (qasm2_code , str )
1241
+ from mpqp .qasm .qasm_to_cirq import qasm2_to_cirq_Circuit
1242
+
1243
+ custom_cirq_circuit = qasm2_to_cirq_Circuit (qasm2_code )
1244
+ cirq_circuit += custom_cirq_circuit
1245
+ self .gphase += custom_circuit .gphase
1246
+ elif isinstance (instruction , ControlledGate ):
1247
+ targets = []
1248
+ for target in instruction .targets :
1249
+ targets .append (cirq_qubits [target ])
1250
+ controls = []
1251
+ for control in instruction .controls :
1252
+ controls .append (cirq_qubits [control ])
1253
+ cirq_instruction = instruction .to_other_language (Language .CIRQ )
1254
+ cirq_circuit .append (cirq_instruction .on (* controls , * targets ))
1255
+ else :
1256
+ targets = []
1257
+ for target in instruction .targets :
1258
+ targets .append (cirq_qubits [target ])
1259
+ cirq_instruction = instruction .to_other_language (Language .CIRQ )
1260
+ cirq_circuit .append (cirq_instruction .on (* targets ))
1160
1261
1161
- cirq_circuit = qasm2_to_cirq_Circuit (qasm2_code )
1162
1262
if cirq_proc_id :
1163
1263
from cirq .transformers .optimize_for_target_gateset import (
1164
1264
optimize_for_target_gateset ,
@@ -1194,7 +1294,7 @@ def to_other_language(
1194
1294
self .gphase = gphase
1195
1295
return qasm_str
1196
1296
elif language == Language .QASM3 :
1197
- qasm2_code = self .to_other_language (Language .QASM2 )
1297
+ qasm2_code = self .to_other_language (Language .QASM2 , skip_pre_measure = True )
1198
1298
if TYPE_CHECKING :
1199
1299
assert isinstance (qasm2_code , str )
1200
1300
from mpqp .qasm .open_qasm_2_and_3 import open_qasm_2_to_3
@@ -1299,14 +1399,19 @@ def __str__(self) -> str:
1299
1399
1300
1400
def __repr__ (self ) -> str :
1301
1401
instructions_repr = ", " .join (repr (instr ) for instr in self .instructions )
1302
- nb_qubits = ""
1402
+ options = []
1303
1403
if self ._user_nb_qubits is not None :
1304
- nb_qubits = f", nb_qubits={ self .nb_qubits } "
1404
+ options .append (f"nb_qubits={ self .nb_qubits } " )
1405
+ if self ._user_nb_cbits is not None :
1406
+ options .append (f"nb_cbits={ self .nb_cbits } " )
1407
+ if self .label is not None :
1408
+ options .append (f'label="{ self .label } ' )
1305
1409
1410
+ options_repr = f", { ', ' .join (options )} " if options else ""
1306
1411
components = instructions_repr + (
1307
- "" if len (self .noises ) ! = 0 else (", " + ", " .join (map (repr , self .noises )))
1412
+ "" if len (self .noises ) = = 0 else (", " + ", " .join (map (repr , self .noises )))
1308
1413
)
1309
- return f'QCircuit([{ components } ]{ nb_qubits } , nb_cbits= { self . nb_cbits } , label=" { self . label } " )'
1414
+ return f'QCircuit([{ components } ]{ options_repr } )'
1310
1415
1311
1416
def variables (self ) -> set [Basic ]:
1312
1417
"""Returns all the symbolic parameters involved in this circuit.
0 commit comments