Skip to content
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

152 basic implementation mapper #160

Merged
merged 17 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 11 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
14 changes: 14 additions & 0 deletions opensquirrel/circuit.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations
elenbaasc marked this conversation as resolved.
Show resolved Hide resolved

from typing import Callable, Dict

import numpy as np
Expand All @@ -9,6 +11,7 @@
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.merger import general_merger
from opensquirrel.parser.libqasm.libqasm_ir_creator import LibqasmIRCreator
from opensquirrel.squirrel_ir import Gate, Measure, SquirrelIR
Expand Down Expand Up @@ -95,6 +98,17 @@ 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:
"""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``.
"""
mapper = IdentityMapper() if mapper is None else mapper
map_qubits(self.squirrel_ir, mapper)

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
Expand Down
4 changes: 4 additions & 0 deletions opensquirrel/mapper/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from opensquirrel.mapper.general_mapper import Mapper, map_qubits
from opensquirrel.mapper.simple_mappers import HardcodedMapper, IdentityMapper

__all__ = ["map_qubits", "Mapper", "IdentityMapper", "HardcodedMapper"]
37 changes: 37 additions & 0 deletions opensquirrel/mapper/general_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""This module contains generic mapping components."""

from __future__ import annotations

from abc import ABC, abstractmethod

from opensquirrel.squirrel_ir import Gate, Measure, SquirrelIR


def map_qubits(squirrel_ir: SquirrelIR, mapper: Mapper) -> None:
"""Map the virtual qubits in the `squirrel_ir` to physical qubits using `mapper`.

Args:
squirrel_ir: IR to apply mapping to.
mapper: Mapping pass to use.
"""

mapping = mapper.map(squirrel_ir)

for statement in squirrel_ir.statements:
if isinstance(statement, (Gate, Measure)):
statement.relabel(mapping)


class Mapper(ABC):
"""Base class for the Mapper pass."""

@abstractmethod
def map(self, squirrel_ir: SquirrelIR) -> dict[int, int]:
"""Produce a mapping from the virtual qubits in the `squirrel_ir` to the physical qubits.

Args:
squirrel_ir: IR to apply mapping to.

Returns:
Mapping from virtual qubits to physical qubits.
"""
59 changes: 59 additions & 0 deletions opensquirrel/mapper/simple_mappers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""This module contains the following simple mappers:

* IdentityMapper
* HardcodedMapper
"""

from __future__ import annotations

from collections.abc import Mapping
from typing import SupportsInt

from opensquirrel.mapper.general_mapper import Mapper
from opensquirrel.squirrel_ir import SquirrelIR


class IdentityMapper(Mapper):

def map(self, squirrel_ir: SquirrelIR) -> dict[int, int]:
"""Produce an IdentityMapping.

Args:
squirrel_ir: IR to map.

Returns:
Mapping from virtual qubits to physical qubits. Each virtual qubit is mapped to the physical qubit with
the same index.
"""
return {i: i for i in range(squirrel_ir.number_of_qubits)}


class HardcodedMapper(Mapper):

def __init__(self, mapping: Mapping[SupportsInt, SupportsInt]) -> None:
"""Init of the ``HardcodedMapper``.

Args:
mapping: Mapping with as keys the virtual qubits and as values the physical qubits.

Raises:
ValueError: If `mapping` is not a valid mapping.
"""
# Check if the provided mapping is valid
self.mapping = {int(virtual_qubit): int(physical_qubit) for virtual_qubit, physical_qubit in mapping.items()}
if set(self.mapping.keys()) != set(self.mapping.values()):
raise ValueError("The set of physical qubits is not equal to the set of virtual qubits.")

def map(self, squirrel_ir: SquirrelIR) -> dict[int, int]:
"""Retrieve the hardcoded mapping.

Args:
squirrel_ir: IR to apply mapping to.

Returns:
Mapping from virtual qubits to physical qubits.
"""
if set(range(squirrel_ir.number_of_qubits)) != set(self.mapping.keys()):
raise ValueError("Virtual qubits are not labeled correctly.")

return self.mapping
22 changes: 22 additions & 0 deletions opensquirrel/mapper/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import networkx as nx

from opensquirrel.squirrel_ir import Gate, SquirrelIR


def make_interaction_graph(squirrel_ir: SquirrelIR) -> nx.Graph:

interaction_graph = nx.Graph()
gates = (statement for statement in squirrel_ir.statements if isinstance(statement, Gate))

for gate in gates:
target_qubits = gate.get_qubit_operands()
if len(target_qubits) == 1:
continue
if len(target_qubits) > 2:
raise ValueError(
f"The gate {gate} acts on more than 2 qubits. The gate must be decomposed before an interaction graph "
"can be made."
)
interaction_graph.add_edge(*target_qubits)

return interaction_graph
56 changes: 51 additions & 5 deletions opensquirrel/squirrel_ir.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from __future__ import annotations

import inspect
from abc import ABC, abstractmethod
from collections.abc import Mapping
from dataclasses import dataclass
from functools import wraps
from typing import Callable, List, Optional, Tuple
Expand Down Expand Up @@ -108,14 +111,22 @@ def name(self) -> Optional[str]:
def __eq__(self, other):
if not isinstance(other, Measure):
return False
return self.qubit == other.qubit and self.axis == other.axis
return self.qubit == other.qubit and np.allclose(self.axis, other.axis, atol=ATOL)

def accept(self, visitor: SquirrelIRVisitor):
visitor.visit_measure(self)

def get_qubit_operands(self) -> List[Qubit]:
return [self.qubit]

def relabel(self, mapping: Mapping[int, int]) -> None:
"""Relabel the qubits using the given mapping.

Args:
mapping: Mapping from the indices of the original qubits to the indices of the qubits after replacement.
"""
self.qubit = Qubit(mapping[self.qubit.index])


class Gate(Statement, ABC):
# Note: two gates are considered equal even when their generators/arguments are different.
Expand All @@ -140,12 +151,19 @@ def is_anonymous(self) -> bool:
return self.arguments is None

@abstractmethod
def get_qubit_operands(self) -> List[Qubit]:
raise NotImplementedError
def get_qubit_operands(self) -> List[Qubit]: ...
S-Linde marked this conversation as resolved.
Show resolved Hide resolved

@abstractmethod
def is_identity(self) -> bool:
raise NotImplementedError
def is_identity(self) -> bool: ...

@abstractmethod
def relabel(self, mapping: Mapping[int, int]) -> None:
"""Relabel the qubits using the given mapping.

Args:
mapping: Mapping from the indices of the original qubits to the indices of the qubits
after replacement.
"""


class BlochSphereRotation(Gate):
Expand Down Expand Up @@ -200,6 +218,15 @@ def is_identity(self) -> bool:
# Angle and phase are already normalized.
return abs(self.angle) < ATOL and abs(self.phase) < ATOL

def relabel(self, mapping: Mapping[int, int]) -> None:
"""Relabel the qubits using the given mapping.

Args:
mapping: Mapping from the indices of the original qubits to the indices of the qubits
after replacement.
"""
self.qubit = Qubit(mapping[self.qubit.index])


class MatrixGate(Gate):
generator: Optional[Callable[..., "MatrixGate"]] = None
Expand All @@ -225,6 +252,15 @@ def get_qubit_operands(self) -> List[Qubit]:
def is_identity(self) -> bool:
return np.allclose(self.matrix, np.eye(2 ** len(self.operands)))

def relabel(self, mapping: Mapping[int, int]) -> None:
"""Relabel the qubits using the given mapping.

Args:
mapping: Mapping from the indices of the original qubits to the indices of the qubits
after replacement.
"""
self.operands = [Qubit(mapping[qubit.index]) for qubit in self.operands]


class ControlledGate(Gate):
generator: Optional[Callable[..., "ControlledGate"]] = None
Expand All @@ -247,6 +283,16 @@ def get_qubit_operands(self) -> List[Qubit]:
def is_identity(self) -> bool:
return self.target_gate.is_identity()

def relabel(self, mapping: Mapping[int, int]) -> None:
"""Relabel the qubits using the given mapping.

Args:
mapping: Mapping from the indices of the original qubits to the indices of the qubits
after replacement.
"""
self.control_qubit = Qubit(mapping[self.control_qubit.index])
self.target_gate.relabel(mapping)


def _compare_gate_classes(g1: Gate, g2: Gate) -> bool:
union_mapping = list(set(g1.get_qubit_operands()) | set(g2.get_qubit_operands()))
Expand Down
5 changes: 5 additions & 0 deletions opensquirrel/utils/check_passes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Ths subpackage contains compatibility checks for the different compilation passes."""

from opensquirrel.utils.check_passes.check_mapper import check_mapper

__all__ = ["check_mapper"]
59 changes: 59 additions & 0 deletions opensquirrel/utils/check_passes/check_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""This module contains checks for the ``Mapper`` pass."""

from copy import deepcopy

from opensquirrel.mapper.general_mapper import Mapper
from opensquirrel.squirrel_ir import BlochSphereRotation, Comment, ControlledGate, Measure, Qubit, SquirrelIR


def check_mapper(mapper: Mapper) -> None:
"""Check if the `mapper` complies with the OpenSquirrel requirements.

If an implementation of ``Mapper`` passes these checks it should be compatible with the ``Circuit.map_qubits``
method.

Args:
mapper: Mapper to check.
"""

assert isinstance(mapper, Mapper)

squirrel_ir = SquirrelIR(number_of_qubits=10)
_check_scenario(squirrel_ir, deepcopy(mapper))
S-Linde marked this conversation as resolved.
Show resolved Hide resolved

squirrel_ir = SquirrelIR(number_of_qubits=10)
squirrel_ir.add_comment(Comment("comment"))
squirrel_ir.add_gate(BlochSphereRotation(Qubit(42), (1, 0, 0), 1, 2))
squirrel_ir.add_gate(ControlledGate(Qubit(42), BlochSphereRotation.identity(Qubit(100))))
squirrel_ir.add_measurement(Measure(Qubit(42), (0, 0, 1)))
_check_scenario(squirrel_ir, deepcopy(mapper))


def _check_scenario(squirrel_ir: SquirrelIR, mapper: Mapper) -> None:
"""Check if the given scenario can be mapped.

Args:
squirrel_ir: SquirrelIR containing the scenario to check against.
mapping: Mapping to check.
"""
squirrel_ir_copy = deepcopy(squirrel_ir)
mapping = mapper.map(squirrel_ir)

assert squirrel_ir == squirrel_ir_copy, "A Mapper pass should not change the SquirrelIR."
_check_mapping_format(mapping, squirrel_ir.number_of_qubits)


def _check_mapping_format(mapping: dict[int, int], number_of_qubits: int) -> None:
"""Check if the mapping has the expected format.

Args:
mapping: Mapping to check.
n_qubits: Number of qubits in the circuit.
"""
assert isinstance(
mapping, dict
), f"Output mapping should be an instance of <class 'dict'>, but was of type {type(mapping)}"
assert set(mapping.keys()) == set(
mapping.values()
), "The set of virtual qubits is not equal to the set of phyical qubits."
assert set(range(number_of_qubits)) == set(mapping.keys()), "Virtual qubits are not labeled correctly."
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ numpy = [
{ version = "^1.26", python = "^3.9" },
]
libqasm = "0.6.3"
networkx = "^3.0.0"

[tool.poetry.group.dev.dependencies]
black = ">=23.11,<25.0"
Expand Down
Empty file added test/mapper/__init__.py
Empty file.
Loading