Skip to content

Commit 224711e

Browse files
Merge pull request #119 from ColibrITD-SAS/feat-to-other-language-cirq
feat: to_other_language cirq without QASM2.0
2 parents e5e7ae2 + d011122 commit 224711e

File tree

13 files changed

+368
-56
lines changed

13 files changed

+368
-56
lines changed

mpqp/core/circuit.py

+132-27
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,15 @@ def __init__(
130130
):
131131
if data is None:
132132
data = []
133-
self.nb_cbits = nb_cbits
134-
"""See parameter description."""
135133
self.label = label
136134
"""See parameter description."""
137135
self.instructions: list[Instruction] = []
138136
"""List of instructions of the circuit."""
139137
self.noises: list[NoiseModel] = []
140138
"""List of noise models attached to the circuit."""
139+
self._user_nb_cbits: Optional[int] = None
140+
self._nb_cbits: int
141+
141142
self._user_nb_qubits: Optional[int] = None
142143
self._nb_qubits: int
143144

@@ -146,6 +147,10 @@ def __init__(
146147
to OpenQASM2. It is used to correct the global phase when the job type
147148
is STATE_VECTOR, and when this circuit contains CustomGate."""
148149

150+
if nb_cbits is None:
151+
self._nb_cbits = 0
152+
else:
153+
self._user_nb_cbits = nb_cbits
149154
if isinstance(data, int):
150155
if data < 0:
151156
raise TypeCheckError(
@@ -161,7 +166,7 @@ def __init__(
161166
connections: set[int] = set.union(
162167
*(instruction.connections() for instruction in data)
163168
)
164-
self._nb_qubits = max(connections) + 1
169+
self._nb_qubits = max(connections, default=0) + 1
165170
else:
166171
self._user_nb_qubits = nb_qubits
167172
self.add(deepcopy(data))
@@ -235,13 +240,11 @@ def add(self, components: OneOrMany[Instruction | NoiseModel]):
235240

236241
self._check_components_targets(components)
237242
if isinstance(components, BasisMeasure):
238-
if self.nb_cbits is None:
239-
self.nb_cbits = 0
240243
if components.c_targets is None:
241244
components.c_targets = [
242245
self.nb_cbits + i for i in range(len(components.targets))
243246
]
244-
self.nb_cbits = max(self.nb_cbits, max(components.c_targets) + 1)
247+
self._update_cbits(max(components.c_targets) + 1)
245248

246249
if isinstance(components, NoiseModel):
247250
self.noises.append(components)
@@ -273,6 +276,15 @@ def _check_components_targets(self, components: Instruction | NoiseModel):
273276
"In noisy circuits, BasisMeasure must span all qubits in the circuit."
274277
)
275278

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+
276288
def _update_targets_components(self, component: Instruction | NoiseModel):
277289
"""Update the targets of the component with the number of qubits in the circuit.
278290
@@ -326,8 +338,6 @@ def _update_targets_components(self, component: Instruction | NoiseModel):
326338

327339
component.basis.set_size(self.nb_qubits)
328340

329-
if self.nb_cbits is None:
330-
self.nb_cbits = 0
331341
unique_cbits = set()
332342
for instruction in self.instructions:
333343
if instruction != component and isinstance(instruction, BasisMeasure):
@@ -345,23 +355,44 @@ def _update_targets_components(self, component: Instruction | NoiseModel):
345355
c_targets.append(i)
346356
i += 1
347357
component.c_targets = c_targets
348-
self.nb_cbits = max(
349-
max(c_targets, default=0) + 1, max(unique_cbits, default=0) + 1
350-
)
351358

359+
self._update_cbits(
360+
max(max(c_targets, default=0) + 1, max(unique_cbits, default=0) + 1)
361+
)
352362
return component
353363

354364
@property
355365
def nb_qubits(self) -> int:
356366
"""Number of qubits of the circuit."""
357367
return self._nb_qubits if self._user_nb_qubits is None else self._user_nb_qubits
358368

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+
359374
@nb_qubits.setter
360375
def nb_qubits(self, nb_qubits: int):
361376
if self._user_nb_qubits is None or self._user_nb_qubits != nb_qubits:
362377
self._user_nb_qubits = nb_qubits
363378
self._set_nb_qubits_dynamic(nb_qubits)
364379

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+
365396
def _set_nb_qubits_dynamic(self, nb_qubits: int):
366397
if not hasattr(self, "_nb_qubits") or nb_qubits != self._nb_qubits:
367398
self._nb_qubits = nb_qubits
@@ -905,7 +936,7 @@ def without_measurements(self) -> QCircuit:
905936
906937
"""
907938
new_circuit = deepcopy(self)
908-
new_circuit.nb_cbits = 0
939+
new_circuit._nb_cbits = 0
909940
new_circuit.instructions = [
910941
inst for inst in self.instructions if not isinstance(inst, Measure)
911942
]
@@ -956,15 +987,36 @@ def without_noises(self) -> QCircuit:
956987
new_circuit.noises = []
957988
return new_circuit
958989

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+
9591004
def to_other_language(
9601005
self,
9611006
language: Language = Language.QISKIT,
9621007
cirq_proc_id: Optional[str] = None,
9631008
translation_warning: bool = True,
1009+
skip_pre_measure: bool = False,
9641010
) -> QuantumCircuit | myQLM_Circuit | braket_Circuit | cirq_Circuit | str:
9651011
"""Transforms this circuit into the corresponding circuit in the language
9661012
specified in the ``language`` arg.
9671013
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+
9681020
By default, the circuit is translated to the corresponding
9691021
``QuantumCircuit`` in Qiskit since this is the interface we use to
9701022
generate the OpenQASM code.
@@ -975,7 +1027,9 @@ def to_other_language(
9751027
9761028
Args:
9771029
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.
9791033
9801034
Returns:
9811035
The corresponding circuit in the target language.
@@ -1021,7 +1075,15 @@ def to_other_language(
10211075
circuits.
10221076
10231077
"""
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
10251087
if language == Language.QISKIT:
10261088
from qiskit.circuit import Operation, QuantumCircuit
10271089
from qiskit.circuit.quantumcircuit import CircuitInstruction
@@ -1031,7 +1093,7 @@ def to_other_language(
10311093
# added parameters, and we use those instead of new ones when they
10321094
# are used more than once
10331095
qiskit_parameters = set()
1034-
if self.nb_cbits is None:
1096+
if self.nb_cbits == 0:
10351097
new_circ = QuantumCircuit(self.nb_qubits)
10361098
else:
10371099
new_circ = QuantumCircuit(self.nb_qubits, self.nb_cbits)
@@ -1103,7 +1165,9 @@ def to_other_language(
11031165

11041166
elif language == Language.MY_QLM:
11051167
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+
)
11071171
self.gphase = cleaned_circuit.gphase
11081172
if TYPE_CHECKING:
11091173
assert isinstance(qasm2_code, str)
@@ -1140,7 +1204,7 @@ def to_other_language(
11401204
)
11411205

11421206
qasm3_code = circuit.to_other_language(
1143-
Language.QASM3, translation_warning=False
1207+
Language.QASM3, translation_warning=False, skip_pre_measure=True
11441208
)
11451209
self.gphase = circuit.gphase
11461210
if TYPE_CHECKING:
@@ -1153,12 +1217,48 @@ def to_other_language(
11531217
self.nb_qubits,
11541218
)
11551219
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))
11601261

1161-
cirq_circuit = qasm2_to_cirq_Circuit(qasm2_code)
11621262
if cirq_proc_id:
11631263
from cirq.transformers.optimize_for_target_gateset import (
11641264
optimize_for_target_gateset,
@@ -1194,7 +1294,7 @@ def to_other_language(
11941294
self.gphase = gphase
11951295
return qasm_str
11961296
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)
11981298
if TYPE_CHECKING:
11991299
assert isinstance(qasm2_code, str)
12001300
from mpqp.qasm.open_qasm_2_and_3 import open_qasm_2_to_3
@@ -1299,14 +1399,19 @@ def __str__(self) -> str:
12991399

13001400
def __repr__(self) -> str:
13011401
instructions_repr = ", ".join(repr(instr) for instr in self.instructions)
1302-
nb_qubits = ""
1402+
options = []
13031403
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}')
13051409

1410+
options_repr = f", {', '.join(options)}" if options else ""
13061411
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)))
13081413
)
1309-
return f'QCircuit([{components}]{nb_qubits}, nb_cbits={self.nb_cbits}, label="{self.label}")'
1414+
return f'QCircuit([{components}]{options_repr})'
13101415

13111416
def variables(self) -> set[Basic]:
13121417
"""Returns all the symbolic parameters involved in this circuit.

mpqp/core/instruction/__init__.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,13 @@
33
from .barrier import Barrier
44
from .gates import *
55
from .breakpoint import Breakpoint
6-
from .measurement import *
6+
from .measurement import (
7+
Basis,
8+
ComputationalBasis,
9+
HadamardBasis,
10+
VariableSizeBasis,
11+
BasisMeasure,
12+
ExpectationMeasure,
13+
Observable,
14+
Measure,
15+
)

0 commit comments

Comments
 (0)