Skip to content

Initial register management implementation #176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 10 additions & 26 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ For creation of a circuit through Python, the `CircuitBuilder` can be used accor
from opensquirrel import CircuitBuilder
from opensquirrel.squirrel_ir import Qubit, Int, Float

my_circuit_from_builder = CircuitBuilder(number_of_qubits=2).ry(Qubit(0), Float(0.23)).cnot(Qubit(0), Qubit(1)).to_circuit()
my_circuit_from_builder = CircuitBuilder(qubit_register_size=2).ry(Qubit(0), Float(0.23)).cnot(Qubit(0), Qubit(1)).to_circuit()
my_circuit_from_builder
```

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

```python
builder = CircuitBuilder(number_of_qubits=10)
builder = CircuitBuilder(qubit_register_size=10)
for i in range(0, 10, 2):
builder.h(Qubit(i))

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

```python
number_of_qubits = 5
qft = CircuitBuilder(number_of_qubits=number_of_qubits)
for i in range(number_of_qubits):
qubit_register_size = 5
qft = CircuitBuilder(qubit_register_size)
for i in range(qubit_register_size):
qft.h(Qubit(i))
for c in range(i + 1, number_of_qubits):
for c in range(i + 1, qubit_register_size):
qft.crk(Qubit(c), Qubit(i), Int(c-i+1))

qft.to_circuit()
Expand Down Expand Up @@ -117,24 +117,6 @@ qft.to_circuit()

As you can see, gates require _strong types_. For instance, you cannot do:

```python
try:
Circuit.from_string(
"""
version 3.0
qubit[2] q

cnot q[0], 3 // The CNOT expects a qubit as second argument.
"""
)
except Exception as e:
print(e)
```

Argument #1 passed to gate `cnot` is of type <class 'opensquirrel.squirrel_ir.Int'> but should be <class 'opensquirrel.squirrel_ir.Qubit'>

The issue is that the CNOT expects a qubit as second input argument, where an integer has been provided. By default, OpenSquirrel does not use LibQASM (the cQASM parser library), but will do so soon. You can enable this, which changes the error message:

```python
try:
Circuit.from_string(
Expand All @@ -152,11 +134,13 @@ except Exception as e:

Parsing error: Error at <unknown>:5:9..13: failed to resolve overload for cnot with argument pack (qubit, int)

The issue is that the CNOT expects a qubit as second input argument, where an integer has been provided.

The same holds for the `CircuitBuilder`, _i.e._, it also throws an error if arguments are passed of an unexpected type:

```python
try:
CircuitBuilder(number_of_qubits=2).cnot(Qubit(0), Int(3))
CircuitBuilder(qubit_register_size=2).cnot(Qubit(0), Int(3))
except Exception as e:
print(e)
```
Expand All @@ -172,7 +156,7 @@ OpenSquirrel can merge consecutive quantum gates. Currently, this is only done f
```python
import math

builder = CircuitBuilder(number_of_qubits=1)
builder = CircuitBuilder(qubit_register_size=1)
for i in range(16):
builder.rx(Qubit(0), Float(math.pi / 16))

Expand Down
48 changes: 29 additions & 19 deletions opensquirrel/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
from opensquirrel.default_measurements import default_measurement_set
from opensquirrel.exporter import quantify_scheduler_exporter, writer
from opensquirrel.exporter.export_format import ExportFormat
from opensquirrel.mapper import IdentityMapper, Mapper, map_qubits
from opensquirrel.mapper import IdentityMapper, Mapper
from opensquirrel.merger import general_merger
from opensquirrel.parser.libqasm.libqasm_ir_creator import LibqasmIRCreator
from opensquirrel.parser.libqasm.libqasm_parser import LibqasmParser
from opensquirrel.register_manager import RegisterManager
from opensquirrel.squirrel_ir import Gate, Measure, SquirrelIR


Expand Down Expand Up @@ -42,9 +43,10 @@ class Circuit:

"""

def __init__(self, squirrel_ir: SquirrelIR):
"""Create a circuit object from a SquirrelIR object."""
def __init__(self, register_manager: RegisterManager, squirrel_ir: SquirrelIR):
"""Create a circuit object from a register manager and an IR."""

self.register_manager = register_manager
self.squirrel_ir = squirrel_ir

@classmethod
Expand All @@ -58,33 +60,41 @@ def from_string(
"""Create a circuit object from a cQasm3 string. All the gates in the circuit need to be defined in
the `gates` argument.

* type-checking is performed, eliminating qubit indices errors and incoherencies
* type-checking is performed, eliminating qubit indices errors and incoherences
* checks that used gates are supported and mentioned in `gates` with appropriate signatures
* does not support map or variables, and other things...
* for example of `gates` dictionary, please look at TestGates.py


Args:
cqasm3_string: a cqasm 3 string
cqasm3_string: a cQASM 3 string
gate_set: an array of gate semantic functions. See default_gates for examples
gate_aliases: a dictionary of extra gate aliases, mapping strings to functions in the gate set
measurement_set: an array of measurement semantic functions. See default_measurements for examples

"""
libqasm_ir_creator = LibqasmIRCreator(
libqasm_parser = LibqasmParser(
gate_set=gate_set,
gate_aliases=gate_aliases,
measurement_set=measurement_set,
)
return Circuit(libqasm_ir_creator.squirrel_ir_from_string(cqasm3_string))
return libqasm_parser.circuit_from_string(cqasm3_string)

@property
def number_of_qubits(self) -> int:
return self.squirrel_ir.number_of_qubits
def qubit_register_size(self) -> int:
return self.register_manager.register_size

@property
def qubit_register_name(self) -> str:
return self.squirrel_ir.qubit_register_name
return self.register_manager.qubit_register_name

@property
def bit_register_size(self) -> int:
return self.register_manager.register_size

@property
def bit_register_name(self) -> str:
return self.register_manager.bit_register_name

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

def map_qubits(self, mapper: Mapper | None = None) -> None:
def map(self, mapper: Mapper = None) -> None:
"""Generic qubit mapper pass.

Maps the virtual qubits of the circuit to physical qubits of the target hardware.
Maps the (virtual) qubits of the circuit to physical qubits of the target hardware.

Args:
mapper: Mapper pass to use. If ``None`` (default) is provided, use the ``IdentityMapper``.
"""
mapper = IdentityMapper() if mapper is None else mapper
map_qubits(self.squirrel_ir, mapper)
mapper = IdentityMapper(qubit_register_size) if mapper is None else mapper
squirrel_ir.register_manager.mapping = mapper.get_map()

def replace(self, gate_generator: Callable[..., Gate], f):
"""Manually replace occurrences of a given gate with a list of gates.
Expand All @@ -125,13 +135,13 @@ def test_get_circuit_matrix(self) -> np.ndarray:
* result is stored as a numpy array of complex numbers

"""
return circuit_matrix_calculator.get_circuit_matrix(self.squirrel_ir)
return circuit_matrix_calculator.get_circuit_matrix(self)

def __repr__(self) -> str:
"""Write the circuit to a cQasm3 string."""
return writer.squirrel_ir_to_string(self.squirrel_ir)
"""Write the circuit to a cQASM 3 string."""
return writer.circuit_to_string(self)

def export(self, fmt: ExportFormat = None) -> None:
if fmt == ExportFormat.QUANTIFY_SCHEDULER:
return quantify_scheduler_exporter.export(self.squirrel_ir)
return quantify_scheduler_exporter.export(self)
raise ValueError("Unknown exporter format")
33 changes: 14 additions & 19 deletions opensquirrel/circuit_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from opensquirrel.circuit import Circuit
from opensquirrel.default_gates import default_gate_aliases, default_gate_set
from opensquirrel.instruction_library import GateLibrary
from opensquirrel.squirrel_ir import Comment, Gate, Qubit, SquirrelIR
from opensquirrel.squirrel_ir import Comment, Gate, SquirrelIR


class CircuitBuilder(GateLibrary):
Expand All @@ -13,31 +13,26 @@ class CircuitBuilder(GateLibrary):
Adds corresponding gate when a method is called. Checks gates are known and called with the right arguments.
Mainly here to allow for Qiskit-style circuit construction:

Example:
>>> CircuitBuilder(number_of_qubits=3).h(Qubit(0)).cnot(Qubit(0), Qubit(1)).cnot(Qubit(0), Qubit(2)). \
to_circuit()
version 3.0
<BLANKLINE>
qubit[3] q
<BLANKLINE>
h q[0]
cnot q[0], q[1]
cnot q[0], q[2]
<BLANKLINE>
>>> CircuitBuilder(qubit_register_size=3).h(Qubit(0)).cnot(Qubit(0), Qubit(1)).cnot(Qubit(0), Qubit(2)).to_circuit()
version 3.0
<BLANKLINE>
qubit[3] q
<BLANKLINE>
h q[0]
cnot q[0], q[1]
cnot q[0], q[2]
<BLANKLINE>
"""

_default_qubit_register_name = "q"

def __init__(
self,
number_of_qubits: int,
qubit_register_size: int,
gate_set: [Callable[..., Gate]] = default_gate_set,
gate_aliases: Dict[str, Callable[..., Gate]] = default_gate_aliases,
):
GateLibrary.__init__(self, gate_set, gate_aliases)
self.squirrel_ir = SquirrelIR(
number_of_qubits=number_of_qubits, qubit_register_name=self._default_qubit_register_name
)
self.register_manager = RegisterManager(qubit_register_size)
self.squirrel_ir = SquirrelIR()

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

def to_circuit(self) -> Circuit:
return Circuit(self.squirrel_ir)
return Circuit(self.register_manager, self.squirrel_ir)
15 changes: 8 additions & 7 deletions opensquirrel/circuit_matrix_calculator.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
import numpy as np

from opensquirrel.circuit import Circuit
from opensquirrel.squirrel_ir import Comment, Gate, SquirrelIR, SquirrelIRVisitor
from opensquirrel.utils.matrix_expander import get_matrix


class _CircuitMatrixCalculator(SquirrelIRVisitor):
def __init__(self, number_of_qubits):
self.number_of_qubits = number_of_qubits
self.matrix = np.eye(1 << self.number_of_qubits, dtype=np.complex128)
def __init__(self, qubit_register_size):
self.qubit_register_size = qubit_register_size
self.matrix = np.eye(1 << self.qubit_register_size, dtype=np.complex128)

def visit_gate(self, gate: Gate):
big_matrix = get_matrix(gate, number_of_qubits=self.number_of_qubits)
big_matrix = get_matrix(gate, qubit_register_size=self.qubit_register_size)
self.matrix = big_matrix @ self.matrix

def visit_comment(self, comment: Comment):
pass


def get_circuit_matrix(squirrel_ir: SquirrelIR):
def get_circuit_matrix(circuit: Circuit):
"""
Compute the Numpy unitary matrix corresponding to the circuit.
The size of this matrix grows exponentially with the number of qubits.
"""

impl = _CircuitMatrixCalculator(squirrel_ir.number_of_qubits)
impl = _CircuitMatrixCalculator(circuit.qubit_register_size)

squirrel_ir.accept(impl)
circuit.squirrel_ir.accept(impl)

return impl.matrix
16 changes: 5 additions & 11 deletions opensquirrel/exporter/quantify_scheduler_exporter.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import math

from opensquirrel.circuit import Circuit
from opensquirrel.common import ATOL
from opensquirrel.default_gates import X, Z
from opensquirrel.squirrel_ir import (
BlochSphereRotation,
ControlledGate,
MatrixGate,
Qubit,
SquirrelIR,
SquirrelIRVisitor,
)
from opensquirrel.squirrel_ir import BlochSphereRotation, ControlledGate, MatrixGate, Qubit, SquirrelIRVisitor

try:
import quantify_scheduler
Expand Down Expand Up @@ -74,7 +68,7 @@ def visit_controlled_gate(self, g: ControlledGate):
raise _unsupported_gates_exception


def export(squirrel_ir: SquirrelIR):
def export(circuit: Circuit):
if "quantify_scheduler" not in globals():

class QuantifySchedulerNotInstalled:
Expand All @@ -86,6 +80,6 @@ def __getattr__(self, attr_name):
global quantify_scheduler_gates
quantify_scheduler_gates = QuantifySchedulerNotInstalled()

schedule_creator = _ScheduleCreator(squirrel_ir.qubit_register_name)
squirrel_ir.accept(schedule_creator)
schedule_creator = _ScheduleCreator(circuit.qubit_register_name)
circuit.squirrel_ir.accept(schedule_creator)
return schedule_creator.schedule
17 changes: 9 additions & 8 deletions opensquirrel/exporter/writer.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from opensquirrel.squirrel_ir import Comment, Float, Gate, Int, Measure, Qubit, SquirrelIR, SquirrelIRVisitor
from opensquirrel.circuit import Circuit
from opensquirrel.squirrel_ir import Comment, Float, Gate, Int, Measure, Qubit, SquirrelIRVisitor


class _WriterImpl(SquirrelIRVisitor):
number_of_significant_digits = 8

def __init__(self, number_of_qubits, qubit_register_name):
self.qubit_register_name = qubit_register_name
self.output = f"""version 3.0\n\nqubit[{number_of_qubits}] {qubit_register_name}\n\n"""
def __init__(self, register_manager):
self.register_manager = register_manager
self.output = f"""version 3.0\n\nqubit[{self.register_manager.qubit_register_size}] {self.register_manager.qubit_register_name}\n\n"""

def visit_qubit(self, qubit: Qubit):
return f"{self.qubit_register_name}[{qubit.index}]"
return f"{self.register_manager.qubit_register_name}[{self.register_manager.get_physical_qubit_index[qubit.index]}]"

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


def squirrel_ir_to_string(squirrel_ir: SquirrelIR):
writer_impl = _WriterImpl(squirrel_ir.number_of_qubits, squirrel_ir.qubit_register_name)
def circuit_to_string(circuit: Circuit):
writer_impl = _WriterImpl(circuit.register_manager)

squirrel_ir.accept(writer_impl)
circuit.squirrel_ir.accept(writer_impl)

return writer_impl.output
Loading