From 64805c76baf40657708c12be3912fbb22c0ebf52 Mon Sep 17 00:00:00 2001 From: rturrado <68099809+rturrado@users.noreply.github.com> Date: Thu, 2 May 2024 13:23:17 +0200 Subject: [PATCH] Fix all tests. TODO: check get_matrix_after_qubit_remapping. - Can this code be updated to use the new Mapping class? - Check qubit_remapper, _QubitReIndexer. --- opensquirrel/circuit.py | 61 ++-- opensquirrel/circuit_builder.py | 1 + opensquirrel/circuit_matrix_calculator.py | 7 +- opensquirrel/exporter/writer.py | 8 +- opensquirrel/mapper/__init__.py | 4 +- opensquirrel/mapper/general_mapper.py | 3 +- opensquirrel/mapper/mapping.py | 16 +- opensquirrel/mapper/simple_mappers.py | 6 +- opensquirrel/merger/general_merger.py | 4 + .../libqasm/{libqasm_parser.py => parser.py} | 9 +- opensquirrel/register_manager.py | 17 +- .../utils/check_passes/check_mapper.py | 3 +- .../test_quantify_scheduler_exporter.py | 12 +- test/ir_equality_test_base.py | 1 - test/mapper/test_general_mapper.py | 25 +- test/mapper/test_mapping.py | 15 + test/mapper/test_simple_mappers.py | 27 +- test/test_integration.py | 337 +++++++++--------- test/test_libqasm.py | 48 ++- test/test_merger.py | 3 +- test/test_replacer.py | 6 +- test/test_writer.py | 19 +- 22 files changed, 304 insertions(+), 328 deletions(-) rename opensquirrel/parser/libqasm/{libqasm_parser.py => parser.py} (94%) create mode 100644 test/mapper/test_mapping.py diff --git a/opensquirrel/circuit.py b/opensquirrel/circuit.py index 8de6c120..f65fd5b3 100644 --- a/opensquirrel/circuit.py +++ b/opensquirrel/circuit.py @@ -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 @@ -40,22 +36,29 @@ class Circuit: rz q[0], 1.5707963 x90 q[0] - """ 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. @@ -65,24 +68,24 @@ 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: @@ -90,7 +93,7 @@ def qubit_register_name(self) -> str: @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: @@ -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") diff --git a/opensquirrel/circuit_builder.py b/opensquirrel/circuit_builder.py index ef8b0931..f7d012bb 100644 --- a/opensquirrel/circuit_builder.py +++ b/opensquirrel/circuit_builder.py @@ -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 diff --git a/opensquirrel/circuit_matrix_calculator.py b/opensquirrel/circuit_matrix_calculator.py index 6d41b340..00dc0837 100644 --- a/opensquirrel/circuit_matrix_calculator.py +++ b/opensquirrel/circuit_matrix_calculator.py @@ -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) diff --git a/opensquirrel/exporter/writer.py b/opensquirrel/exporter/writer.py index 88ffbaa2..080e4999 100644 --- a/opensquirrel/exporter/writer.py +++ b/opensquirrel/exporter/writer.py @@ -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}" diff --git a/opensquirrel/mapper/__init__.py b/opensquirrel/mapper/__init__.py index bcd97242..a0557640 100644 --- a/opensquirrel/mapper/__init__.py +++ b/opensquirrel/mapper/__init__.py @@ -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"] diff --git a/opensquirrel/mapper/general_mapper.py b/opensquirrel/mapper/general_mapper.py index 3299a22a..de8cd495 100644 --- a/opensquirrel/mapper/general_mapper.py +++ b/opensquirrel/mapper/general_mapper.py @@ -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) diff --git a/opensquirrel/mapper/mapping.py b/opensquirrel/mapper/mapping.py index d122dab4..e9bffbbb 100644 --- a/opensquirrel/mapper/mapping.py +++ b/opensquirrel/mapper/mapping.py @@ -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 @@ -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) diff --git a/opensquirrel/mapper/simple_mappers.py b/opensquirrel/mapper/simple_mappers.py index 769e0448..673f7767 100644 --- a/opensquirrel/mapper/simple_mappers.py +++ b/opensquirrel/mapper/simple_mappers.py @@ -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): diff --git a/opensquirrel/merger/general_merger.py b/opensquirrel/merger/general_merger.py index 7a7a5590..93c3f5b3 100644 --- a/opensquirrel/merger/general_merger.py +++ b/opensquirrel/merger/general_merger.py @@ -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) } diff --git a/opensquirrel/parser/libqasm/libqasm_parser.py b/opensquirrel/parser/libqasm/parser.py similarity index 94% rename from opensquirrel/parser/libqasm/libqasm_parser.py rename to opensquirrel/parser/libqasm/parser.py index 350bedf5..053cf626 100644 --- a/opensquirrel/parser/libqasm/libqasm_parser.py +++ b/opensquirrel/parser/libqasm/parser.py @@ -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 @@ -16,7 +17,7 @@ } -class LibqasmParser(GateLibrary, MeasurementLibrary): +class Parser(GateLibrary, MeasurementLibrary): def __init__( self, gate_set=default_gate_set, @@ -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 @@ -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)) diff --git a/opensquirrel/register_manager.py b/opensquirrel/register_manager.py index 58b5abd3..622bdf85 100644 --- a/opensquirrel/register_manager.py +++ b/opensquirrel/register_manager.py @@ -1,6 +1,4 @@ -from typing import NewType - -from opensquirrel.mapper.simple_mappers import IdentityMapper +from opensquirrel.mapper import Mapper class RegisterManager: @@ -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] diff --git a/opensquirrel/utils/check_passes/check_mapper.py b/opensquirrel/utils/check_passes/check_mapper.py index 996e7aff..6384806f 100644 --- a/opensquirrel/utils/check_passes/check_mapper.py +++ b/opensquirrel/utils/check_passes/check_mapper.py @@ -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) @@ -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" diff --git a/test/exporter/test_quantify_scheduler_exporter.py b/test/exporter/test_quantify_scheduler_exporter.py index 6f0defa3..ce00775b 100644 --- a/test/exporter/test_quantify_scheduler_exporter.py +++ b/test/exporter/test_quantify_scheduler_exporter.py @@ -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))) @@ -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) diff --git a/test/ir_equality_test_base.py b/test/ir_equality_test_base.py index 84e46494..af4ff8da 100644 --- a/test/ir_equality_test_base.py +++ b/test/ir_equality_test_base.py @@ -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) diff --git a/test/mapper/test_general_mapper.py b/test/mapper/test_general_mapper.py index 59337708..50f1a8bb 100644 --- a/test/mapper/test_general_mapper.py +++ b/test/mapper/test_general_mapper.py @@ -4,25 +4,27 @@ from opensquirrel import Circuit from opensquirrel.default_gates import CNOT, H -from opensquirrel.mapper import HardcodedMapper, Mapper, map_qubits +from opensquirrel.mapper import HardcodedMapper, Mapper +from opensquirrel.mapper.mapping import Mapping +from opensquirrel.register_manager import RegisterManager from opensquirrel.squirrel_ir import Comment, Measure, Qubit, SquirrelIR, Statement class TestMapper: def test_init(self) -> None: with pytest.raises(TypeError): - Mapper(qubit_register_size=1) + Mapper() def test_implementation(self) -> None: class Mapper2(Mapper): pass with pytest.raises(TypeError): - Mapper2(qubit_register_size=1) + Mapper2() class Mapper3(Mapper2): def __init__(self, qubit_register_size: int) -> None: - super().__init__(qubit_register_size, Mapping({0: 0})) + super().__init__(qubit_register_size, Mapping([0])) Mapper3(qubit_register_size=1) @@ -49,18 +51,9 @@ def expected_statements_fixture(self) -> list[Statement]: Measure(Qubit(1), axis=(0, 0, 1)), ] - def test_map_qubits(self, circuit: Circuit, expected_statements: list[Statement]) -> None: - mapper = HardcodedMapper(circuit.qubit_register_size, Mapping({0: 1, 1: 0, 2: 2})) + def test_circuit_map(self, circuit: Circuit, expected_statements: list[Statement]) -> None: + mapper = HardcodedMapper(circuit.qubit_register_size, Mapping([1, 0, 2])) circuit.map(mapper) # Check that the circuit is altered as expected - mapped_statements = circuit.squirrel_ir.statements - assert mapped_statements == expected_statements - - def test_map_qubits_circuit(self, circuit: Circuit, expected_statements: list[Statement]) -> None: - mapper = HardcodedMapper(circuit.qubit_register_size, Mapping({0: 1, 1: 0, 2: 2})) - circuit.map(mapper) - - # Check that the circuit is altered as expected - mapped_statements = circuit.squirrel_ir.statements - assert mapped_statements == expected_statements + assert circuit.register_manager.mapping == mapper.get_mapping() diff --git a/test/mapper/test_mapping.py b/test/mapper/test_mapping.py new file mode 100644 index 00000000..53b363f7 --- /dev/null +++ b/test/mapper/test_mapping.py @@ -0,0 +1,15 @@ +import pytest + +from opensquirrel.mapper.mapping import Mapping + + +class TestMapping: + def test_1_physical_qubit(self) -> None: + Mapping([0]) + + def test_2_physical_qubits(self) -> None: + Mapping([0, 1]) + + def test_incorrect(self) -> None: + with pytest.raises(ValueError): + Mapping([0, 2]) diff --git a/test/mapper/test_simple_mappers.py b/test/mapper/test_simple_mappers.py index 1953ec16..96df46ed 100644 --- a/test/mapper/test_simple_mappers.py +++ b/test/mapper/test_simple_mappers.py @@ -3,40 +3,31 @@ import pytest from opensquirrel.mapper import HardcodedMapper, IdentityMapper -from opensquirrel.squirrel_ir import SquirrelIR +from opensquirrel.mapper.mapping import Mapping from opensquirrel.utils.check_passes import check_mapper class TestIdentityMapper: @pytest.fixture(name="mapper") def mapper_fixture(self) -> IdentityMapper: - return IdentityMapper() + return IdentityMapper(qubit_register_size=3) def test_compliance(self, mapper: IdentityMapper) -> None: check_mapper(mapper) - def test_mapping(self, mapper: IdentityMapper) -> None: - squirrel_ir = SquirrelIR(qubit_register_size=3) - mapping = mapper.map(squirrel_ir) - assert mapping == {0: 0, 1: 1, 2: 2} + def test_get_mapping(self, mapper: IdentityMapper) -> None: + assert mapper.get_mapping() == Mapping([0, 1, 2]) class TestHardcodedMapper: - @pytest.fixture(name="mapper") def mapper_fixture(self) -> HardcodedMapper: - mapping = {i: (i + 1) % 10 for i in range(10)} - return HardcodedMapper(mapping) + qubit_register_size = 10 + mapping = Mapping([(i + 1) % qubit_register_size for i in range(qubit_register_size)]) + return HardcodedMapper(qubit_register_size, mapping) def test_compliance(self, mapper: HardcodedMapper) -> None: check_mapper(mapper) - def test_mapping(self, mapper: HardcodedMapper) -> None: - squirrel_ir = SquirrelIR(qubit_register_size=10) - mapping = mapper.map(squirrel_ir) - assert mapping == {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 0} - - @pytest.mark.parametrize("incorrect_mapping", [{0: 0, 1: 0}, {0: 1}]) - def test_errors(self, incorrect_mapping: dict[int, int]) -> None: - with pytest.raises(ValueError): - HardcodedMapper(incorrect_mapping) + def test_get_mapping(self, mapper: HardcodedMapper) -> None: + assert mapper.get_mapping() == Mapping([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) diff --git a/test/test_integration.py b/test/test_integration.py index 5ae819ba..ed0452d8 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -13,17 +13,17 @@ class IntegrationTest(unittest.TestCase): def test_simple(self): - myCircuit = Circuit.from_string( + circuit = Circuit.from_string( """ version 3.0 - qubit[3] qreg + qubit[3] q - Ry qreg[0], 1.23 - Ry qreg[1], 2.34 - CNOT qreg[0], qreg[1] - Rx qreg[0], -2.3 - Ry qreg[1], -3.14 + Ry q[0], 1.23 + Ry q[1], 2.34 + CNOT q[0], q[1] + Rx q[0], -2.3 + Ry q[1], -3.14 """ ) @@ -34,7 +34,7 @@ def test_simple(self): # -----⊕---- --- H --•-- H --- # - myCircuit.replace( + circuit.replace( CNOT, lambda control, target: [ H(target), @@ -44,164 +44,157 @@ def test_simple(self): ) # Do 1q-gate fusion and decomposer with McKay decomposition. - - myCircuit.merge_single_qubit_gates() - - myCircuit.decompose(decomposer=McKayDecomposer) + circuit.merge_single_qubit_gates() + circuit.decompose(decomposer=McKayDecomposer) # Write the transformed circuit as a cQasm3 string. - - output = str(myCircuit) - self.assertEqual( - output, + str(circuit), """version 3.0 -qubit[3] qreg - -Rz qreg[0], 3.1415927 -X90 qreg[0] -Rz qreg[0], 1.9115927 -X90 qreg[0] -Rz qreg[1], 3.1415927 -X90 qreg[1] -Rz qreg[1], 2.372389 -X90 qreg[1] -Rz qreg[1], 3.1415927 -CZ qreg[0], qreg[1] -Rz qreg[0], 1.5707963 -X90 qreg[0] -Rz qreg[0], 0.84159265 -X90 qreg[0] -Rz qreg[0], 1.5707963 -Rz qreg[1], 3.1415927 -X90 qreg[1] -Rz qreg[1], 1.572389 -X90 qreg[1] -Rz qreg[1], 3.1415927 -""", - ) +qubit[3] q + +Rz q[0], 3.1415927 +X90 q[0] +Rz q[0], 1.9115927 +X90 q[0] +Rz q[1], 3.1415927 +X90 q[1] +Rz q[1], 2.372389 +X90 q[1] +Rz q[1], 3.1415927 +CZ q[0], q[1] +Rz q[0], 1.5707963 +X90 q[0] +Rz q[0], 0.84159265 +X90 q[0] +Rz q[0], 1.5707963 +Rz q[1], 3.1415927 +X90 q[1] +Rz q[1], 1.572389 +X90 q[1] +Rz q[1], 3.1415927 +""") def test_measurement(self): - myCircuit = Circuit.from_string( + circuit = Circuit.from_string( """ version 3.0 - qubit[3] qreg + qubit[3] q - Ry qreg[2], 2.34 - Rz qreg[0], 1.5707963 - Ry qreg[0], -0.2 - CNOT qreg[1], qreg[0] - Rz qreg[0], 1.5789 - CNOT qreg[1], qreg[0] - Rz qreg[1], 2.5707963 - measure qreg[0,2] + Ry q[2], 2.34 + Rz q[0], 1.5707963 + Ry q[0], -0.2 + CNOT q[1], q[0] + Rz q[0], 1.5789 + CNOT q[1], q[0] + Rz q[1], 2.5707963 + measure q[0,2] """, ) - myCircuit.merge_single_qubit_gates() - myCircuit.decompose(decomposer=McKayDecomposer) + circuit.merge_single_qubit_gates() + circuit.decompose(decomposer=McKayDecomposer) self.assertEqual( - str(myCircuit), + str(circuit), """version 3.0 -qubit[3] qreg - -Rz qreg[0], 1.5707963 -X90 qreg[0] -Rz qreg[0], 2.9415927 -X90 qreg[0] -Rz qreg[0], 3.1415927 -CNOT qreg[1], qreg[0] -Rz qreg[0], -2.3521427 -X90 qreg[0] -Rz qreg[0], 3.1415927 -X90 qreg[0] -Rz qreg[0], 0.78945 -CNOT qreg[1], qreg[0] -Rz qreg[2], 3.1415927 -X90 qreg[2] -Rz qreg[2], 0.80159265 -X90 qreg[2] -Rz qreg[1], -1.8561945 -X90 qreg[1] -Rz qreg[1], 3.1415927 -X90 qreg[1] -Rz qreg[1], 1.2853981 -measure qreg[0] -measure qreg[2] -""", - ) +qubit[3] q + +Rz q[0], 1.5707963 +X90 q[0] +Rz q[0], 2.9415927 +X90 q[0] +Rz q[0], 3.1415927 +CNOT q[1], q[0] +Rz q[0], -2.3521427 +X90 q[0] +Rz q[0], 3.1415927 +X90 q[0] +Rz q[0], 0.78945 +CNOT q[1], q[0] +Rz q[2], 3.1415927 +X90 q[2] +Rz q[2], 0.80159265 +X90 q[2] +Rz q[1], -1.8561945 +X90 q[1] +Rz q[1], 3.1415927 +X90 q[1] +Rz q[1], 1.2853981 +measure q[0] +measure q[2] +""") def test_consecutive_measurements(self): - myCircuit = Circuit.from_string( + circuit = Circuit.from_string( """ version 3.0 - qubit[3] qreg + qubit[3] q - H qreg[0] - H qreg[1] - H qreg[2] - measure qreg[0] - measure qreg[1] - measure qreg[2] + H q[0] + H q[1] + H q[2] + measure q[0] + measure q[1] + measure q[2] """ ) - myCircuit.merge_single_qubit_gates() - myCircuit.decompose(decomposer=McKayDecomposer) + circuit.merge_single_qubit_gates() + circuit.decompose(decomposer=McKayDecomposer) self.assertEqual( - str(myCircuit), + str(circuit), """version 3.0 -qubit[3] qreg - -X90 qreg[0] -Rz qreg[0], 1.5707963 -X90 qreg[0] -X90 qreg[1] -Rz qreg[1], 1.5707963 -X90 qreg[1] -X90 qreg[2] -Rz qreg[2], 1.5707963 -X90 qreg[2] -measure qreg[0] -measure qreg[1] -measure qreg[2] -""", +qubit[3] q + +X90 q[0] +Rz q[0], 1.5707963 +X90 q[0] +X90 q[1] +Rz q[1], 1.5707963 +X90 q[1] +X90 q[2] +Rz q[2], 1.5707963 +X90 q[2] +measure q[0] +measure q[1] +measure q[2] +""" ) def test_measure_order(self): - myCircuit = Circuit.from_string( + circuit = Circuit.from_string( """ version 3.0 - qubit[2] qreg + qubit[2] q - Rz qreg[1], -2.3561945 - Rz qreg[1], 1.5707963 - measure qreg[1,0] + Rz q[1], -2.3561945 + Rz q[1], 1.5707963 + measure q[1,0] """ ) - myCircuit.merge_single_qubit_gates() - myCircuit.decompose(decomposer=McKayDecomposer) - output = str(myCircuit) - expected = """version 3.0 - -qubit[2] qreg - -Rz qreg[1], 2.7488936 -X90 qreg[1] -Rz qreg[1], 3.1415927 -X90 qreg[1] -Rz qreg[1], -0.3926991 -measure qreg[1] -measure qreg[0] -""" - self.assertEqual(output, expected) + circuit.merge_single_qubit_gates() + circuit.decompose(decomposer=McKayDecomposer) + self.assertEqual( + str(circuit), + """version 3.0 + +qubit[2] q + +Rz q[1], 2.7488936 +X90 q[1] +Rz q[1], 3.1415927 +X90 q[1] +Rz q[1], -0.3926991 +measure q[1] +measure q[0] +""") def test_qi(self): - myCircuit = Circuit.from_string( + circuit = Circuit.from_string( """ version 3.0 @@ -230,12 +223,12 @@ def test_qi(self): """ ) - myCircuit.merge_single_qubit_gates() + circuit.merge_single_qubit_gates() + circuit.decompose(decomposer=McKayDecomposer) - myCircuit.decompose(decomposer=McKayDecomposer) - output = str(myCircuit) - - expected = """version 3.0 + self.assertEqual( + str(circuit), + """version 3.0 qubit[4] q @@ -264,42 +257,40 @@ def test_qi(self): Rz q[1], 1.5707964 X90 q[1] Rz q[1], 3.1415927 -""" - - self.assertEqual(output, expected) +""") def test_libqasm_error(self): with self.assertRaisesRegex( Exception, - r"Parsing error: Error at :4:21\.\.23: failed to resolve instruction 'Ry' with argument pack \(qubit, float, int\)", + r"Parsing error: Error at :4:17\.\.19: failed to resolve instruction 'Ry' with argument pack \(qubit, float, int\)", ): Circuit.from_string( """ - version 3.0 - qubit[3] qreg - Ry qreg[0], 1.23, 1 + version 3.0 + qubit[3] q + Ry q[0], 1.23, 1 """, ) def test_export_quantify_scheduler(self): - myCircuit = Circuit.from_string( + circuit = Circuit.from_string( """ - version 3.0 + version 3.0 - qubit[3] qreg + qubit[3] q - H qreg[1] - CZ qreg[0], qreg[1] - CNOT qreg[0], qreg[1] - CRk qreg[0], qreg[1], 4 - H qreg[0] + H q[1] + CZ q[0], q[1] + CNOT q[0], q[1] + CRk q[0], q[1], 4 + H q[0] """ ) - myCircuit.decompose(decomposer=CNOTDecomposer) + circuit.decompose(decomposer=CNOTDecomposer) # Quantify-scheduler prefers CZ. - myCircuit.replace( + circuit.replace( CNOT, lambda control, target: [ H(target), @@ -309,19 +300,19 @@ def test_export_quantify_scheduler(self): ) # Reduce gate count by single-qubit gate fusion. - myCircuit.merge_single_qubit_gates() + circuit.merge_single_qubit_gates() # FIXME: for best gate count we need a Z-XY decomposer. # See https://github.com/QuTech-Delft/OpenSquirrel/issues/98 - myCircuit.decompose(decomposer=ZYZDecomposer) + circuit.decompose(decomposer=ZYZDecomposer) if importlib.util.find_spec("quantify_scheduler") is None: with self.assertRaisesRegex( Exception, "quantify-scheduler is not installed, or cannot be installed on " "your system" ): - myCircuit.export(fmt=ExportFormat.QUANTIFY_SCHEDULER) + circuit.export(fmt=ExportFormat.QUANTIFY_SCHEDULER) else: - exported_schedule = myCircuit.export(fmt=ExportFormat.QUANTIFY_SCHEDULER) + exported_schedule = circuit.export(fmt=ExportFormat.QUANTIFY_SCHEDULER) self.assertEqual(exported_schedule.name, "Exported OpenSquirrel circuit") @@ -333,25 +324,25 @@ def test_export_quantify_scheduler(self): self.assertEqual( operations, [ - "Rz(-180, 'qreg[1]')", - "Rxy(90, 90, 'qreg[1]')", - "CZ (qreg[0], qreg[1])", - "Rz(-180, 'qreg[1]')", - "Rxy(90, 90, 'qreg[1]')", - "CZ (qreg[0], qreg[1])", - "Rz(90, 'qreg[1]')", - "Rxy(11.25, 90, 'qreg[1]')", - "Rz(-90, 'qreg[1]')", - "CZ (qreg[0], qreg[1])", - "Rz(90, 'qreg[1]')", - "Rxy(-11.25, 90, 'qreg[1]')", - "Rz(-90, 'qreg[1]')", - "CZ (qreg[0], qreg[1])", - "Rz(11.25, 'qreg[0]')", - "Rxy(-90, 90, 'qreg[0]')", - "Rz(-180, 'qreg[0]')", - "Rz(-180, 'qreg[1]')", - "Rxy(90, 90, 'qreg[1]')", + "Rz(-180, 'q[1]')", + "Rxy(90, 90, 'q[1]')", + "CZ (q[0], q[1])", + "Rz(-180, 'q[1]')", + "Rxy(90, 90, 'q[1]')", + "CZ (q[0], q[1])", + "Rz(90, 'q[1]')", + "Rxy(11.25, 90, 'q[1]')", + "Rz(-90, 'q[1]')", + "CZ (q[0], q[1])", + "Rz(90, 'q[1]')", + "Rxy(-11.25, 90, 'q[1]')", + "Rz(-90, 'q[1]')", + "CZ (q[0], q[1])", + "Rz(11.25, 'q[0]')", + "Rxy(-90, 90, 'q[0]')", + "Rz(-180, 'q[0]')", + "Rz(-180, 'q[1]')", + "Rxy(90, 90, 'q[1]')", ], ) diff --git a/test/test_libqasm.py b/test/test_libqasm.py index 0b1517b1..70a8abd5 100644 --- a/test/test_libqasm.py +++ b/test/test_libqasm.py @@ -1,32 +1,31 @@ import unittest from opensquirrel.default_gates import * -from opensquirrel.parser.libqasm.libqasm_parser import LibqasmIRCreator +from opensquirrel.parser.libqasm.parser import Parser class LibqasmTest(unittest.TestCase): def setUp(self): - self.libqasm_ir_creator = LibqasmIRCreator(gate_set=default_gate_set, gate_aliases=default_gate_aliases) + self.parser = Parser(gate_set=default_gate_set, gate_aliases=default_gate_aliases) def test_simple(self): - ir = self.libqasm_ir_creator.squirrel_ir_from_string( - """ + circuit = self.parser.circuit_from_string( +""" version 3.0 -qubit[2] my_qubits +qubit[2] q -H my_qubits[0] -Ry my_qubits[1], 1.234 -CNOT my_qubits[0], my_qubits[1] -CR my_qubits[1], my_qubits[0], 5.123 -CRk my_qubits[0], my_qubits[1], 23 - """ - ) +H q[0] +Ry q[1], 1.234 +CNOT q[0], q[1] +CR q[1], q[0], 5.123 +CRk q[0], q[1], 23 +""") - self.assertEqual(ir.qubit_register_size, 2) - self.assertEqual(ir.qubit_register_name, "my_qubits") + self.assertEqual(circuit.qubit_register_size, 2) + self.assertEqual(circuit.qubit_register_name, "q") self.assertEqual( - ir.statements, + circuit.squirrel_ir.statements, [ H(Qubit(0)), Ry(Qubit(1), Float(1.234)), @@ -37,8 +36,8 @@ def test_simple(self): ) def test_sgmq(self): - ir = self.libqasm_ir_creator.squirrel_ir_from_string( - """ + circuit = self.parser.circuit_from_string( +""" version 3.0 qubit[20] q @@ -46,13 +45,12 @@ def test_sgmq(self): H q[5:9] X q[13,17] CRk q[0, 3], q[1, 4], 23 - """ - ) +""") - self.assertEqual(ir.qubit_register_size, 20) - self.assertEqual(ir.qubit_register_name, "q") + self.assertEqual(circuit.qubit_register_size, 20) + self.assertEqual(circuit.qubit_register_name, "q") self.assertEqual( - ir.statements, + circuit.squirrel_ir.statements, [ H(Qubit(5)), H(Qubit(6)), @@ -68,17 +66,17 @@ def test_sgmq(self): def test_error(self): with self.assertRaisesRegex(Exception, "Error at :1:30..31: failed to resolve variable 'q'"): - self.libqasm_ir_creator.squirrel_ir_from_string("""version 3.0; qubit[20] qu; H q[5]""") + self.parser.circuit_from_string("""version 3.0; qubit[20] qu; H q[5]""") def test_wrong_gate_argument_number_or_types(self): with self.assertRaisesRegex( Exception, r"Parsing error: Error at :1:26\.\.27: failed to resolve instruction 'H' with argument pack \(qubit, int\)", ): - self.libqasm_ir_creator.squirrel_ir_from_string("""version 3.0; qubit[1] q; H q[0], 1""") + self.parser.circuit_from_string("""version 3.0; qubit[1] q; H q[0], 1""") with self.assertRaisesRegex( Exception, r"Parsing error: Error at :1:26\.\.30: failed to resolve instruction 'CNOT' with argument pack \(qubit, int\)", ): - self.libqasm_ir_creator.squirrel_ir_from_string("""version 3.0; qubit[1] q; CNOT q[0], 1""") + self.parser.circuit_from_string("""version 3.0; qubit[1] q; CNOT q[0], 1""") diff --git a/test/test_merger.py b/test/test_merger.py index 8cfa3e13..a7a32280 100644 --- a/test/test_merger.py +++ b/test/test_merger.py @@ -1,6 +1,7 @@ import unittest from test.ir_equality_test_base import IREqualityTestBase +from opensquirrel.circuit import Circuit from opensquirrel.default_gates import * from opensquirrel.merger import general_merger from opensquirrel.merger.general_merger import compose_bloch_sphere_rotations @@ -68,7 +69,7 @@ def test_two_hadamards_different_qubits(self): self.modify_circuit_and_check(circuit, general_merger.merge_single_qubit_gates, expected_circuit) def test_merge_different_qubits(self): - register_manager = RegisterManager(qubit_register_size=4,) + register_manager = RegisterManager(qubit_register_size=4) ir = SquirrelIR() ir.add_gate(Ry(Qubit(0), Float(math.pi / 2))) ir.add_gate(Rx(Qubit(0), Float(math.pi))) diff --git a/test/test_replacer.py b/test/test_replacer.py index 849536fb..3ba86fb1 100644 --- a/test/test_replacer.py +++ b/test/test_replacer.py @@ -1,8 +1,10 @@ import unittest +from opensquirrel.circuit import Circuit from opensquirrel.decomposer import general_decomposer from opensquirrel.decomposer.general_decomposer import Decomposer, check_valid_replacement from opensquirrel.default_gates import * +from opensquirrel.register_manager import RegisterManager from opensquirrel.squirrel_ir import Comment, Qubit, SquirrelIR @@ -104,7 +106,7 @@ def test_replace_generic(self): squirrel_ir = SquirrelIR() squirrel_ir.add_gate(H(Qubit(0))) squirrel_ir.add_gate(CNOT(Qubit(0), Qubit(1))) - circuit = Ciruit(register_manager, squirrel_ir) + circuit = Circuit(register_manager, squirrel_ir) # A simple decomposer function that adds identities before and after single-qubit gates. class TestDecomposer(Decomposer): @@ -129,7 +131,7 @@ def test_replace(self): squirrel_ir = SquirrelIR() squirrel_ir.add_gate(H(Qubit(0))) squirrel_ir.add_comment(Comment("Test comment.")) - circuit = Ciruit(register_manager, squirrel_ir) + circuit = Circuit(register_manager, squirrel_ir) general_decomposer.replace(squirrel_ir, H, lambda q: [Y90(q), X(q)]) diff --git a/test/test_writer.py b/test/test_writer.py index 80e5311d..f32b2e5b 100644 --- a/test/test_writer.py +++ b/test/test_writer.py @@ -16,7 +16,7 @@ def test_write(self): self.assertEqual(writer.circuit_to_string(circuit), """version 3.0 -qubit[3] myqubitsregister +qubit[3] q """, ) @@ -28,34 +28,33 @@ def test_write(self): self.assertEqual(writer.circuit_to_string(circuit), """version 3.0 -qubit[3] myqubitsregister +qubit[3] q -H myqubitsregister[0] -CR myqubitsregister[0], myqubitsregister[1], 1.234 +H q[0] +CR q[0], q[1], 1.234 """, ) def test_anonymous_gate(self): - register_manager = RegisterManager(qubit_register_size=1) + register_manager = RegisterManager(qubit_register_size=2) squirrel_ir = SquirrelIR() squirrel_ir.add_gate(CR(Qubit(0), Qubit(1), Float(1.234))) squirrel_ir.add_gate(BlochSphereRotation(Qubit(0), axis=(1, 1, 1), angle=1.23)) squirrel_ir.add_gate(CR(Qubit(0), Qubit(1), Float(1.234))) circuit = Circuit(register_manager, squirrel_ir) - self.assertEqual(writer.squirrel_ir_to_string(circuit), + self.assertEqual(writer.circuit_to_string(circuit), """version 3.0 -qubit[1] q +qubit[2] q CR q[0], q[1], 1.234 CR q[0], q[1], 1.234 -""", - ) +""") def test_comment(self): - register_manager = RegisterManagar(qubit_register_size=3) + register_manager = RegisterManager(qubit_register_size=3) squirrel_ir = SquirrelIR() squirrel_ir.add_gate(H(Qubit(0))) squirrel_ir.add_comment(Comment("My comment"))