Skip to content

Commit 15a699c

Browse files
committed
WIP.
A circuit has a register manager. A register manager holds: - a (virtual) qubit and bit register size and names, - a mapping. A mapping is a dictionary from a (virtual) qubit register to a physical qubit register. Mapping correctness is a class invariant. I.e., it is checked during the construction of the mapping. Remove relabel code. Qubits always hold their (virtual) qubit index. A circuit, through its register manager, holds the current mapping. Writers can get the physical qubit index for a given qubit through the register manager. Remove IR's number of qubits and qubit register name. This information is accessed through the circuit's register manager.
1 parent b1390bb commit 15a699c

25 files changed

+292
-300
lines changed

docs/tutorial.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ For creation of a circuit through Python, the `CircuitBuilder` can be used accor
4949
from opensquirrel import CircuitBuilder
5050
from opensquirrel.squirrel_ir import Qubit, Int, Float
5151

52-
my_circuit_from_builder = CircuitBuilder(number_of_qubits=2).ry(Qubit(0), Float(0.23)).cnot(Qubit(0), Qubit(1)).to_circuit()
52+
my_circuit_from_builder = CircuitBuilder(qubit_register_size=2).ry(Qubit(0), Float(0.23)).cnot(Qubit(0), Qubit(1)).to_circuit()
5353
my_circuit_from_builder
5454
```
5555

@@ -63,7 +63,7 @@ my_circuit_from_builder
6363
You can naturally use the functionalities available in Python to create your circuit:
6464

6565
```python
66-
builder = CircuitBuilder(number_of_qubits=10)
66+
builder = CircuitBuilder(qubit_register_size=10)
6767
for i in range(0, 10, 2):
6868
builder.h(Qubit(i))
6969

@@ -83,11 +83,11 @@ builder.to_circuit()
8383
For instance, you can generate a quantum fourier transform (QFT) circuit as follows:
8484

8585
```python
86-
number_of_qubits = 5
87-
qft = CircuitBuilder(number_of_qubits=number_of_qubits)
88-
for i in range(number_of_qubits):
86+
qubit_register_size = 5
87+
qft = CircuitBuilder(qubit_register_size)
88+
for i in range(qubit_register_size):
8989
qft.h(Qubit(i))
90-
for c in range(i + 1, number_of_qubits):
90+
for c in range(i + 1, qubit_register_size):
9191
qft.crk(Qubit(c), Qubit(i), Int(c-i+1))
9292

9393
qft.to_circuit()
@@ -156,7 +156,7 @@ The same holds for the `CircuitBuilder`, _i.e._, it also throws an error if argu
156156

157157
```python
158158
try:
159-
CircuitBuilder(number_of_qubits=2).cnot(Qubit(0), Int(3))
159+
CircuitBuilder(qubit_register_size=2).cnot(Qubit(0), Int(3))
160160
except Exception as e:
161161
print(e)
162162
```
@@ -172,7 +172,7 @@ OpenSquirrel can merge consecutive quantum gates. Currently, this is only done f
172172
```python
173173
import math
174174

175-
builder = CircuitBuilder(number_of_qubits=1)
175+
builder = CircuitBuilder(qubit_register_size=1)
176176
for i in range(16):
177177
builder.rx(Qubit(0), Float(math.pi / 16))
178178

opensquirrel/circuit.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
from opensquirrel.exporter.export_format import ExportFormat
1414
from opensquirrel.mapper import IdentityMapper, Mapper, map_qubits
1515
from opensquirrel.merger import general_merger
16-
from opensquirrel.parser.libqasm.libqasm_ir_creator import LibqasmIRCreator
16+
from opensquirrel.parser.libqasm.libqasm_parser import LibqasmParser
17+
from opensquirrel.register_manager import RegisterManager
1718
from opensquirrel.squirrel_ir import Gate, Measure, SquirrelIR
1819

1920

@@ -42,9 +43,10 @@ class Circuit:
4243
4344
"""
4445

45-
def __init__(self, squirrel_ir: SquirrelIR):
46-
"""Create a circuit object from a SquirrelIR object."""
46+
def __init__(self, register_manager: RegisterManager, squirrel_ir: SquirrelIR):
47+
"""Create a circuit object from a register manager and an IR."""
4748

49+
self.register_manager = register_manager
4850
self.squirrel_ir = squirrel_ir
4951

5052
@classmethod
@@ -58,33 +60,41 @@ def from_string(
5860
"""Create a circuit object from a cQasm3 string. All the gates in the circuit need to be defined in
5961
the `gates` argument.
6062
61-
* type-checking is performed, eliminating qubit indices errors and incoherencies
63+
* type-checking is performed, eliminating qubit indices errors and incoherences
6264
* checks that used gates are supported and mentioned in `gates` with appropriate signatures
6365
* does not support map or variables, and other things...
6466
* for example of `gates` dictionary, please look at TestGates.py
6567
6668
6769
Args:
68-
cqasm3_string: a cqasm 3 string
70+
cqasm3_string: a cQASM 3 string
6971
gate_set: an array of gate semantic functions. See default_gates for examples
7072
gate_aliases: a dictionary of extra gate aliases, mapping strings to functions in the gate set
7173
measurement_set: an array of measurement semantic functions. See default_measurements for examples
7274
7375
"""
74-
libqasm_ir_creator = LibqasmIRCreator(
76+
libqasm_parser = LibqasmParser(
7577
gate_set=gate_set,
7678
gate_aliases=gate_aliases,
7779
measurement_set=measurement_set,
7880
)
79-
return Circuit(libqasm_ir_creator.squirrel_ir_from_string(cqasm3_string))
81+
return libqasm_parser.from_string(cqasm3_string)
8082

8183
@property
82-
def number_of_qubits(self) -> int:
83-
return self.squirrel_ir.number_of_qubits
84+
def qubit_register_size(self) -> int:
85+
return self.register_manager.register_size
8486

8587
@property
8688
def qubit_register_name(self) -> str:
87-
return self.squirrel_ir.qubit_register_name
89+
return self.register_manager.qubit_register_name
90+
91+
@property
92+
def bit_register_size(self) -> int:
93+
return self.register_manager.register_size
94+
95+
@property
96+
def bit_register_name(self) -> str:
97+
return self.register_manager.bit_register_name
8898

8999
def merge_single_qubit_gates(self):
90100
"""Merge all consecutive 1-qubit gates in the circuit.
@@ -98,16 +108,16 @@ def decompose(self, decomposer: Decomposer):
98108
"""Generic decomposition pass. It applies the given decomposer function to every gate in the circuit."""
99109
general_decomposer.decompose(self.squirrel_ir, decomposer)
100110

101-
def map_qubits(self, mapper: Mapper | None = None) -> None:
111+
def map(self, mapper: Mapper = None) -> None:
102112
"""Generic qubit mapper pass.
103113
104-
Maps the virtual qubits of the circuit to physical qubits of the target hardware.
114+
Maps the (virtual) qubits of the circuit to physical qubits of the target hardware.
105115
106116
Args:
107117
mapper: Mapper class to use. If ``None`` (default) is provided, use the ``IdentityMapper``.
108118
"""
109-
mapper = IdentityMapper() if mapper is None else mapper
110-
map_qubits(self.squirrel_ir, mapper)
119+
mapper = IdentityMapper(circuit.qubit_register_size) if mapper is None else mapper
120+
register_manager.mapping = mapper.get_map()
111121

112122
def replace(self, gate_generator: Callable[..., Gate], f):
113123
"""Manually replace occurrences of a given gate with a list of gates.
@@ -125,13 +135,13 @@ def test_get_circuit_matrix(self) -> np.ndarray:
125135
* result is stored as a numpy array of complex numbers
126136
127137
"""
128-
return circuit_matrix_calculator.get_circuit_matrix(self.squirrel_ir)
138+
return circuit_matrix_calculator.get_circuit_matrix(self)
129139

130140
def __repr__(self) -> str:
131-
"""Write the circuit to a cQasm3 string."""
132-
return writer.squirrel_ir_to_string(self.squirrel_ir)
141+
"""Write the circuit to a cQASM 3 string."""
142+
return writer.to_string(self)
133143

134144
def export(self, fmt: ExportFormat = None) -> None:
135145
if fmt == ExportFormat.QUANTIFY_SCHEDULER:
136-
return quantify_scheduler_exporter.export(self.squirrel_ir)
146+
return quantify_scheduler_exporter.export(self)
137147
raise ValueError("Unknown exporter format")

opensquirrel/circuit_builder.py

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from opensquirrel.circuit import Circuit
55
from opensquirrel.default_gates import default_gate_aliases, default_gate_set
6-
from opensquirrel.instruction_library import GateLibrary
6+
from opensquirrel.gate_library import GateLibrary
77
from opensquirrel.squirrel_ir import Comment, Gate, Qubit, SquirrelIR
88

99

@@ -13,31 +13,26 @@ class CircuitBuilder(GateLibrary):
1313
Adds corresponding gate when a method is called. Checks gates are known and called with the right arguments.
1414
Mainly here to allow for Qiskit-style circuit construction:
1515
16-
Example:
17-
>>> CircuitBuilder(number_of_qubits=3).h(Qubit(0)).cnot(Qubit(0), Qubit(1)).cnot(Qubit(0), Qubit(2)). \
18-
to_circuit()
19-
version 3.0
20-
<BLANKLINE>
21-
qubit[3] q
22-
<BLANKLINE>
23-
h q[0]
24-
cnot q[0], q[1]
25-
cnot q[0], q[2]
26-
<BLANKLINE>
16+
>>> CircuitBuilder(qubit_register_size=3).h(Qubit(0)).cnot(Qubit(0), Qubit(1)).cnot(Qubit(0), Qubit(2)).to_circuit()
17+
version 3.0
18+
<BLANKLINE>
19+
qubit[3] q
20+
<BLANKLINE>
21+
h q[0]
22+
cnot q[0], q[1]
23+
cnot q[0], q[2]
24+
<BLANKLINE>
2725
"""
2826

29-
_default_qubit_register_name = "q"
30-
3127
def __init__(
3228
self,
33-
number_of_qubits: int,
29+
qubit_register_size: int,
3430
gate_set: [Callable[..., Gate]] = default_gate_set,
3531
gate_aliases: Dict[str, Callable[..., Gate]] = default_gate_aliases,
3632
):
3733
GateLibrary.__init__(self, gate_set, gate_aliases)
38-
self.squirrel_ir = SquirrelIR(
39-
number_of_qubits=number_of_qubits, qubit_register_name=self._default_qubit_register_name
40-
)
34+
self.register_manager = RegisterManager(qubit_register_size)
35+
self.squirrel_ir = SquirrelIR()
4136

4237
def __getattr__(self, attr):
4338
def add_comment(comment_string: str):
@@ -59,4 +54,4 @@ def add_this_gate(*args):
5954
return add_comment if attr == "comment" else add_this_gate
6055

6156
def to_circuit(self) -> Circuit:
62-
return Circuit(self.squirrel_ir)
57+
return Circuit(self.register_manager, self.squirrel_ir)
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
import numpy as np
22

3+
from opensquirrel.circuit import Circuit
34
from opensquirrel.squirrel_ir import Comment, Gate, SquirrelIR, SquirrelIRVisitor
45
from opensquirrel.utils.matrix_expander import get_matrix
56

67

78
class _CircuitMatrixCalculator(SquirrelIRVisitor):
8-
def __init__(self, number_of_qubits):
9-
self.number_of_qubits = number_of_qubits
10-
self.matrix = np.eye(1 << self.number_of_qubits, dtype=np.complex128)
9+
def __init__(self, qubit_register_size):
10+
self.qubit_register_size = qubit_register_size
11+
self.matrix = np.eye(1 << self.qubit_register_size, dtype=np.complex128)
1112

1213
def visit_gate(self, gate: Gate):
13-
big_matrix = get_matrix(gate, number_of_qubits=self.number_of_qubits)
14+
big_matrix = get_matrix(gate, qubit_register_size=self.qubit_register_size)
1415
self.matrix = big_matrix @ self.matrix
1516

1617
def visit_comment(self, comment: Comment):
1718
pass
1819

1920

20-
def get_circuit_matrix(squirrel_ir: SquirrelIR):
21+
def get_circuit_matrix(circuit: Circuit):
2122
"""
2223
Compute the Numpy unitary matrix corresponding to the circuit.
2324
The size of this matrix grows exponentially with the number of qubits.
2425
"""
2526

26-
impl = _CircuitMatrixCalculator(squirrel_ir.number_of_qubits)
27+
impl = _CircuitMatrixCalculator(circuit.qubit_register_size)
2728

28-
squirrel_ir.accept(impl)
29+
circuit.squirrel_ir.accept(impl)
2930

3031
return impl.matrix

opensquirrel/exporter/quantify_scheduler_exporter.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def visit_controlled_gate(self, g: ControlledGate):
7474
raise _unsupported_gates_exception
7575

7676

77-
def export(squirrel_ir: SquirrelIR):
77+
def export(circuit: Circuit):
7878
if "quantify_scheduler" not in globals():
7979

8080
class QuantifySchedulerNotInstalled:
@@ -86,6 +86,6 @@ def __getattr__(self, attr_name):
8686
global quantify_scheduler_gates
8787
quantify_scheduler_gates = QuantifySchedulerNotInstalled()
8888

89-
schedule_creator = _ScheduleCreator(squirrel_ir.qubit_register_name)
90-
squirrel_ir.accept(schedule_creator)
89+
schedule_creator = _ScheduleCreator(circuit.qubit_register_name)
90+
circuit.squirrel_ir.accept(schedule_creator)
9191
return schedule_creator.schedule

opensquirrel/exporter/writer.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1+
from opensquirrel.circuit import Circuit
12
from opensquirrel.squirrel_ir import Comment, Float, Gate, Int, Measure, Qubit, SquirrelIR, SquirrelIRVisitor
23

34

45
class _WriterImpl(SquirrelIRVisitor):
56
number_of_significant_digits = 8
67

7-
def __init__(self, number_of_qubits, qubit_register_name):
8-
self.qubit_register_name = qubit_register_name
9-
self.output = f"""version 3.0\n\nqubit[{number_of_qubits}] {qubit_register_name}\n\n"""
8+
def __init__(self, register_manager):
9+
self.qubit_register_name = register_manager.qubit_register_name
10+
self.output = f"""version 3.0\n\nqubit[{register_manager.register_size}] {self.qubit_register_name}\n\n"""
1011

1112
def visit_qubit(self, qubit: Qubit):
12-
return f"{self.qubit_register_name}[{qubit.index}]"
13+
return f"{self.qubit_register_name}[{register_manager.get_physical_qubit_index[qubit.index]}]"
1314

1415
def visit_int(self, i: Int):
1516
return f"{i.value}"
@@ -32,9 +33,9 @@ def visit_comment(self, comment: Comment):
3233
self.output += f"\n/* {comment.str} */\n\n"
3334

3435

35-
def squirrel_ir_to_string(squirrel_ir: SquirrelIR):
36-
writer_impl = _WriterImpl(squirrel_ir.number_of_qubits, squirrel_ir.qubit_register_name)
36+
def to_string(circuit: Circuit):
37+
writer_impl = _WriterImpl(circuit.register_manager)
3738

38-
squirrel_ir.accept(writer_impl)
39+
circuit.squirrel_ir.accept(writer_impl)
3940

4041
return writer_impl.output

opensquirrel/mapper/general_mapper.py

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,20 @@
22

33
from __future__ import annotations
44

5-
from abc import ABC, abstractmethod
5+
from opensquirrel.mapping import Mapping
6+
from opensquirrel.register_manager import PhysicalQubitRegister
67

7-
from opensquirrel.squirrel_ir import Gate, Measure, SquirrelIR
88

9-
10-
def map_qubits(squirrel_ir: SquirrelIR, mapper: Mapper) -> None:
11-
"""Map the virtual qubits in the `squirrel_ir` to physical qubits using `mapper`.
12-
13-
Args:
14-
squirrel_ir: IR to act on.
15-
mapper: Mapping algorithm to use.
16-
"""
17-
18-
mapping = mapper.map(squirrel_ir)
19-
20-
for statement in squirrel_ir.statements:
21-
if isinstance(statement, (Gate, Measure)):
22-
statement.relabel(mapping)
23-
24-
25-
class Mapper(ABC):
9+
class Mapper:
2610
"""Base class for the Mapper pass."""
2711

28-
@abstractmethod
29-
def map(self, squirrel_ir: SquirrelIR) -> dict[int, int]:
30-
"""Produce a mapping between thee virtual qubits in the `squirrel_ir` to physical qubits.
12+
def __init__(self, qubit_register_size: int, mapping: Mapping = None) -> None:
13+
"""Use ``IdentityMapper`` as the fallback case for ``Mapper``"""
14+
self.mapping = mapping if mapping is not None else Mapping(PhysicalQubitRegister(range(qubit_register_size)))
3115

32-
Args:
33-
squirrel_ir: IR to map.
16+
if qubit_register_size != self.mapping.size():
17+
raise ValueError("Qubit register size and mapping size differ.")
3418

35-
Returns:
36-
Dictionary with as keys the virtual qubits and as values the physical qubits.
37-
"""
19+
def get_mapping(self) -> Mapping:
20+
"""Get mapping."""
21+
return self.mapping

opensquirrel/mapper/mapping.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from opensquirrel.register_manager import PhysicalQubitIndex
2+
3+
4+
class Mapping:
5+
"""A Mapping is a dictionary where:
6+
- the keys are virtual qubit indices (from 0 to virtual_qubit_register_size-1), and
7+
- the values are physical qubit indices.
8+
9+
Args:
10+
physical_qubit_register: a list of physical qubit indices.
11+
12+
Raises:
13+
ValueError: If the mapping is incorrect.
14+
"""
15+
def __init__(self, physical_qubit_register: list[PhysicalQubitIndex]) -> None:
16+
self.data = dict(enumerate(physical_qubit_register))
17+
if (self.data.keys()) != set(self.data.values()):
18+
raise ValueError("The mapping is incorrect.")

0 commit comments

Comments
 (0)