Skip to content

Commit

Permalink
Fix all tests.
Browse files Browse the repository at this point in the history
TODO: check get_matrix_after_qubit_remapping.
- Can this code be updated to use the new Mapping class?
- Check qubit_remapper, _QubitReIndexer.
  • Loading branch information
rturrado committed May 2, 2024
1 parent b7e4900 commit 64805c7
Show file tree
Hide file tree
Showing 22 changed files with 304 additions and 328 deletions.
61 changes: 23 additions & 38 deletions opensquirrel/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@

import numpy as np

from opensquirrel import circuit_matrix_calculator
from opensquirrel.decomposer import general_decomposer
from opensquirrel.decomposer.general_decomposer import Decomposer
from opensquirrel.default_gates import default_gate_aliases, default_gate_set
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
from opensquirrel.merger import general_merger
from opensquirrel.parser.libqasm.libqasm_parser import LibqasmParser
from opensquirrel.register_manager import RegisterManager
from opensquirrel.squirrel_ir import Gate, Measure, SquirrelIR

Expand All @@ -40,22 +36,29 @@ class Circuit:
rz q[0], 1.5707963
x90 q[0]
<BLANKLINE>
"""

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

def __repr__(self) -> str:
"""Write the circuit to a cQASM 3 string."""
from opensquirrel.exporter import writer
return writer.circuit_to_string(self)

def __eq__(self, other):
return self.register_manager == other.register_manager and \
self.squirrel_ir == other.squirrel_ir

@classmethod
def from_string(
cls,
cqasm3_string: str,
gate_set: [Callable[..., Gate]] = default_gate_set,
gate_aliases: Dict[str, Callable[..., Gate]] = default_gate_aliases,
measurement_set: [Callable[..., Measure]] = default_measurement_set,
measurement_set: [Callable[..., Measure]] = default_measurement_set
):
"""Create a circuit object from a cQasm3 string. All the gates in the circuit need to be defined in
the `gates` argument.
Expand All @@ -65,32 +68,32 @@ def from_string(
* 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
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_parser = LibqasmParser(
from opensquirrel.parser.libqasm.parser import Parser

parser = Parser(
gate_set=gate_set,
gate_aliases=gate_aliases,
measurement_set=measurement_set,
)
return libqasm_parser.circuit_from_string(cqasm3_string)
return parser.circuit_from_string(cqasm3_string)

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

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

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

@property
def bit_register_name(self) -> str:
Expand All @@ -100,48 +103,30 @@ def merge_single_qubit_gates(self):
"""Merge all consecutive 1-qubit gates in the circuit.
Gates obtained from merging other gates become anonymous gates.
"""
general_merger.merge_single_qubit_gates(self.squirrel_ir)
from opensquirrel.merger import general_merger

general_merger.merge_single_qubit_gates(self)

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(self, mapper: Mapper = None) -> None:
def map(self, mapper: Mapper) -> None:
"""Generic qubit mapper pass.
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``.
Update the register manager's mapping with a given mapper's mapping.
"""
mapper = IdentityMapper(qubit_register_size) if mapper is None else mapper
squirrel_ir.register_manager.mapping = mapper.get_map()
self.register_manager.mapping = mapper.get_mapping()

def replace(self, gate_generator: Callable[..., Gate], f):
"""Manually replace occurrences of a given gate with a list of gates.
`f` is a callable that takes the arguments of the gate that is to be replaced
and returns the decomposition as a list of gates.
"""
general_decomposer.replace(self.squirrel_ir, gate_generator, f)

def test_get_circuit_matrix(self) -> np.ndarray:
"""Get the (large) unitary matrix corresponding to the circuit.
* this matrix has 4**n elements, where n is the number of qubits
* therefore this function is only here for testing purposes on small number of qubits
* result is stored as a numpy array of complex numbers
"""
return circuit_matrix_calculator.get_circuit_matrix(self)

def __repr__(self) -> str:
"""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:
from opensquirrel.exporter import quantify_scheduler_exporter
return quantify_scheduler_exporter.export(self)
raise ValueError("Unknown exporter format")
1 change: 1 addition & 0 deletions opensquirrel/circuit_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +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.register_manager import RegisterManager
from opensquirrel.squirrel_ir import Comment, Gate, SquirrelIR


Expand Down
7 changes: 3 additions & 4 deletions opensquirrel/circuit_matrix_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ def visit_comment(self, comment: Comment):


def get_circuit_matrix(circuit: Circuit):
"""Compute the (large) unitary matrix corresponding to the circuit.
This matrix has 4**n elements, where n is the number of qubits.
Result is stored as a numpy array of complex numbers.
"""
Compute the Numpy unitary matrix corresponding to the circuit.
The size of this matrix grows exponentially with the number of qubits.
"""

impl = _CircuitMatrixCalculator(circuit.qubit_register_size)

circuit.squirrel_ir.accept(impl)
Expand Down
8 changes: 6 additions & 2 deletions opensquirrel/exporter/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ class _WriterImpl(SquirrelIRVisitor):

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"""
qubit_register_size = self.register_manager.qubit_register_size
qubit_register_name = self.register_manager.qubit_register_name
self.output = f"""version 3.0\n\nqubit[{qubit_register_size}] {qubit_register_name}\n\n"""

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

def visit_int(self, i: Int):
return f"{i.value}"
Expand Down
4 changes: 2 additions & 2 deletions opensquirrel/mapper/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from opensquirrel.mapper.general_mapper import Mapper, map_qubits
from opensquirrel.mapper.general_mapper import Mapper
from opensquirrel.mapper.simple_mappers import HardcodedMapper, IdentityMapper

__all__ = ["map_qubits", "Mapper", "IdentityMapper", "HardcodedMapper"]
__all__ = ["Mapper", "IdentityMapper", "HardcodedMapper"]
3 changes: 1 addition & 2 deletions opensquirrel/mapper/general_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
from __future__ import annotations

from opensquirrel.mapper.mapping import Mapping
from opensquirrel.register_manager import PhysicalQubitRegister


class Mapper:
"""Base class for the Mapper pass."""

def __init__(self, qubit_register_size: int, mapping: Mapping = None) -> None:
def __init__(self, qubit_register_size: int, mapping: Mapping | None = None) -> None:
"""Use ``IdentityMapper`` as the fallback case for ``Mapper``"""
physical_qubit_register = range(qubit_register_size)
self.mapping = mapping if mapping is not None else Mapping(physical_qubit_register)
Expand Down
16 changes: 11 additions & 5 deletions opensquirrel/mapper/mapping.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
from opensquirrel.register_manager import PhysicalQubitIndex


class Mapping:
"""A Mapping is a dictionary where:
- the keys are virtual qubit indices (from 0 to virtual_qubit_register_size-1), and
Expand All @@ -12,7 +9,16 @@ class Mapping:
Raises:
ValueError: If the mapping is incorrect.
"""
def __init__(self, physical_qubit_register: list[PhysicalQubitIndex]) -> None:
self.data = dict(enumerate(physical_qubit_register))
def __init__(self, physical_qubit_register: list[int]) -> None:
self.data: dict[int, int] = dict(enumerate(physical_qubit_register))
if (self.data.keys()) != set(self.data.values()):
raise ValueError("The mapping is incorrect.")

def __eq__(self, other):
return self.data == other.data

def __getitem__(self, key: int) -> int:
return self.data[key]

def size(self) -> int:
return len(self.data)
6 changes: 1 addition & 5 deletions opensquirrel/mapper/simple_mappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@
* HardcodedMapper
"""

from __future__ import annotations

from collections.abc import Mapping
from typing import SupportsInt

from opensquirrel.mapper.general_mapper import Mapper
from opensquirrel.mapper.mapping import Mapping


class IdentityMapper(Mapper):
Expand Down
4 changes: 4 additions & 0 deletions opensquirrel/merger/general_merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ def compose_bloch_sphere_rotations(a: BlochSphereRotation, b: BlochSphereRotatio


def merge_single_qubit_gates(circuit: Circuit):
"""Merge all consecutive 1-qubit gates in the circuit.
Gates obtained from merging other gates become anonymous gates.
"""
accumulators_per_qubit: dict[Qubit, BlochSphereRotation] = {
Qubit(q): BlochSphereRotation.identity(Qubit(q)) for q in range(circuit.qubit_register_size)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import cqasm.v3x as cqasm

from opensquirrel.circuit import Circuit
from opensquirrel.default_gates import default_gate_aliases, default_gate_set
from opensquirrel.default_measurements import default_measurement_set
from opensquirrel.instruction_library import GateLibrary, MeasurementLibrary
Expand All @@ -16,7 +17,7 @@
}


class LibqasmParser(GateLibrary, MeasurementLibrary):
class Parser(GateLibrary, MeasurementLibrary):
def __init__(
self,
gate_set=default_gate_set,
Expand Down Expand Up @@ -135,11 +136,11 @@ def circuit_from_string(self, s: str):
# Analysis result will be either an Abstract Syntax Tree (AST) or a list of error messages
analyzer = self._create_analyzer()
analysis_result = analyzer.analyze_string(s)
LibqasmParser._check_analysis_result(analysis_result)
Parser._check_analysis_result(analysis_result)
ast = analysis_result

# Parse qubit register
[qubit_register_size, qubit_register_name] = LibqasmParser._parse_qubit_register(ast)
[qubit_register_size, qubit_register_name] = Parser._parse_qubit_register(ast)
register_manager = RegisterManager(qubit_register_size, qubit_register_name)

# Parse statements
Expand All @@ -149,7 +150,7 @@ def circuit_from_string(self, s: str):
generator_f = self.get_measurement_f(statement.name[2:-1])
else:
generator_f = self.get_gate_f(statement.name[2:-1])
expanded_args = LibqasmParser._get_expanded_statement_args(generator_f, statement.operands)
expanded_args = Parser._get_expanded_statement_args(generator_f, statement.operands)
for arg_set in expanded_args:
squirrel_ir.add_gate(generator_f(*arg_set))

Expand Down
17 changes: 5 additions & 12 deletions opensquirrel/register_manager.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
from typing import NewType

from opensquirrel.mapper.simple_mappers import IdentityMapper
from opensquirrel.mapper import Mapper


class RegisterManager:
Expand All @@ -15,19 +13,14 @@ class RegisterManager:
def __init__(
self,
qubit_register_size: int,
bit_register_size: int,
qubit_register_name: str = _default_qubit_register_name,
bit_register_name: str = _default_bit_register_name
) -> None:
self.qubit_register_size = qubit_register_size
self.bit_register_size = bit_register_size
self.bit_register_size = qubit_register_size
self.qubit_register_name = qubit_register_name
self.bit_register_name = bit_register_name
self.mapping = IdentityMapper(register_size).get_mapping()

def get_physical_qubit_index(self, qubit_index: QubitIndex):
return self.mapping.data[qubit_index]

self.mapping = Mapper(qubit_register_size).get_mapping()

QubitIndex = NewType('QubitIndex', int)
PhysicalQubitIndex = NewType('PhysicalQubitIndex', int)
def get_physical_qubit_index(self, qubit_index: int) -> int:
return self.mapping[qubit_index]
3 changes: 1 addition & 2 deletions opensquirrel/utils/check_passes/check_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ def check_mapper(mapper: Mapper) -> None:
Args:
mapper: Mapper to check.
"""

assert isinstance(mapper, Mapper)

register_manager = RegisterManager(qubit_register_size=10)
Expand All @@ -45,4 +44,4 @@ def _check_scenario(circuit: Circuit, mapper: Mapper) -> None:
"""
squirrel_ir_copy = deepcopy(circuit.squirrel_ir)
circuit.map(mapper)
assert squirrel_ir == squirrel_ir_copy, "A Mapper pass should not change the SquirrelIR"
assert circuit.squirrel_ir == squirrel_ir_copy, "A Mapper pass should not change the SquirrelIR"
12 changes: 6 additions & 6 deletions test/exporter/test_quantify_scheduler_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback):

class QuantifySchedulerExporterTest(unittest.TestCase):
def test_export(self):
register_manager = RegisterManager(qubit_register_size=2)
register_manager = RegisterManager(qubit_register_size=3)
squirrel_ir = SquirrelIR()
squirrel_ir.add_gate(X(Qubit(0)))
squirrel_ir.add_gate(CZ(Qubit(0), Qubit(1)))
Expand All @@ -57,18 +57,18 @@ def test_export(self):

mock_quantify_scheduler_gates.Rxy.assert_has_calls(
[
unittest.mock.call(theta=FloatEq(math.degrees(math.pi)), phi=FloatEq(0), qubit="test[0]"),
unittest.mock.call(theta=FloatEq(math.degrees(math.pi)), phi=FloatEq(0), qubit="q[0]"),
unittest.mock.call(
theta=FloatEq(math.degrees(1.23)), phi=FloatEq(math.degrees(math.pi / 2)), qubit="test[2]"
theta=FloatEq(math.degrees(1.23)), phi=FloatEq(math.degrees(math.pi / 2)), qubit="q[2]"
),
]
)
mock_quantify_scheduler_gates.CZ.assert_called_once_with(qC="test[0]", qT="test[1]")
mock_quantify_scheduler_gates.Rz.assert_called_once_with(theta=FloatEq(math.degrees(2.34)), qubit="test[1]")
mock_quantify_scheduler_gates.CZ.assert_called_once_with(qC="q[0]", qT="q[1]")
mock_quantify_scheduler_gates.Rz.assert_called_once_with(theta=FloatEq(math.degrees(2.34)), qubit="q[1]")
self.assertEqual(mock_schedule.add.call_count, 4)

def check_gate_not_supported(self, g: Gate):
register_manager = RegisterManagar(qubit_register_size=20)
register_manager = RegisterManager(qubit_register_size=3)
squirrel_ir = SquirrelIR()
squirrel_ir.add_gate(g)

Expand Down
1 change: 0 additions & 1 deletion test/ir_equality_test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ def modify_circuit_and_check(self, circuit, action, expected_circuit=None):
- the qubit register name(s),
- the circuit matrix up to a global phase factor.
"""

# Store matrix before decompositions.
expected_matrix = circuit_matrix_calculator.get_circuit_matrix(circuit)

Expand Down
Loading

0 comments on commit 64805c7

Please sign in to comment.