Skip to content

API reference

qoolqit

A Python library for algorithm development in the Rydberg Analog Model.

Modules:

Classes:

  • AnalogDevice

    A realistic device for analog sequence execution.

  • Blackman

    A Blackman window of a specified duration and area under the curve.

  • Constant

    A constant waveform over a given duration.

  • DataGraph

    The main graph structure to represent problem data.

  • Delay

    An empty waveform.

  • Device

    QoolQit Device wrapper around a Pulser BaseDevice.

  • DigitalAnalogDevice

    A device with digital and analog capabilities.

  • Drive

    The drive Hamiltonian acting over a duration.

  • Interpolated

    A waveform created from interpolation of a set of data points.

  • MockDevice

    A virtual device for unconstrained prototyping.

  • PiecewiseLinear

    A piecewise linear waveform.

  • QuantumProgram

    A program representing a Sequence acting on a Register of qubits.

  • Ramp

    A ramp that linearly interpolates between an initial and final value.

  • Register

    The Register in QoolQit, representing a set of qubits with coordinates.

  • SequenceCompiler

    Compiles a QoolQit Register and Drive to a Device.

  • Sin

    An arbitrary sine over a given duration.

Functions:

AnalogDevice()

A realistic device for analog sequence execution.

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(self) -> None:
    super().__init__(pulser_device=pulser.AnalogDevice)

specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection
    (RemoteConnection) –

    connection object to fetch the available devices.

  • name
    (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)

info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)

reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter

set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)

set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)

Blackman(duration: float, area: float)

A Blackman window of a specified duration and area under the curve.

Implements the Blackman window shaped waveform blackman(t) = A(0.42 - 0.5cos(αt) + 0.08cos(2αt)) A = area/(0.42duration) α = 2π/duration

See: https://en.wikipedia.org/wiki/Window_function#:~:text=Blackman%20window

Parameters:

  • duration

    (float) –

    The waveform duration.

  • area

    (float) –

    The integral of the waveform.

Example
blackman_wf = Blackman(100.0, area=3.14)

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(self, duration: float, area: float) -> None:
    """Initializes a new Blackman waveform."""
    super().__init__(duration, area=area)

duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

Constant(duration: float, value: float)

A constant waveform over a given duration.

Parameters:

  • duration

    (float) –

    the total duration.

  • value

    (float) –

    the value to take during the duration.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    value: float,
) -> None:
    super().__init__(duration, value=value)

duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

DataGraph(edges: Iterable = [])

The main graph structure to represent problem data.

Parameters:

  • edges

    (Iterable, default: [] ) –

    set of edge tuples (i, j)

Methods:

  • circle

    Constructs a circle graph, with the respective coordinates.

  • distances

    Returns a dictionary of distances for a given set of edges.

  • draw

    Draw the graph.

  • from_coordinates

    Construct a base graph from a set of coordinates.

  • from_matrix

    Constructs a graph from a symmetric square matrix.

  • from_nodes

    Construct a base graph from a set of nodes.

  • from_nx

    Convert a NetworkX Graph object into a QoolQit graph instance.

  • from_pyg

    Convert a PyTorch Geometric Data object into a DataGraph instance.

  • heavy_hexagonal

    Constructs a heavy-hexagonal lattice graph, with respective coordinates.

  • hexagonal

    Constructs a hexagonal lattice graph, with respective coordinates.

  • interactions

    Rydberg model interaction 1/r^6 between pair of nodes.

  • is_ud_graph

    Check if the graph is unit-disk.

  • line

    Constructs a line graph, with the respective coordinates.

  • max_distance

    Returns the maximum distance in the graph.

  • min_distance

    Returns the minimum distance in the graph.

  • random_er

    Constructs an Erdős–Rényi random graph.

  • random_ud

    Constructs a random unit-disk graph.

  • rescale_coords

    Rescales the node coordinates by a factor.

  • set_ud_edges

    Reset the set of edges to be equal to the set of unit-disk edges.

  • square

    Constructs a square lattice graph, with respective coordinates.

  • to_pyg

    Convert the DataGraph to a PyTorch Geometric Data object.

  • triangular

    Constructs a triangular lattice graph, with respective coordinates.

  • ud_edges

    Returns the set of edges given by the intersection of circles of a given radius.

  • ud_radius_range

    Return the range (R_min, R_max) where the graph is unit-disk.

Attributes:

  • all_node_pairs (set) –

    Return a list of all possible node pairs in the graph.

  • coords (dict) –

    Return the dictionary of node coordinates.

  • edge_weights (dict) –

    Return the dictionary of edge weights.

  • has_coords (bool) –

    Check if the graph has coordinates.

  • has_edge_weights (bool) –

    Check if the graph has edge weights.

  • has_edges (bool) –

    Check if the graph has edges.

  • has_node_weights (bool) –

    Check if the graph has node weights.

  • node_weights (dict) –

    Return the dictionary of node weights.

  • sorted_edges (set) –

    Returns the set of edges (u, v) such that (u < v).

Source code in qoolqit/graphs/data_graph.py
def __init__(self, edges: Iterable = []) -> None:
    """
    Default constructor for the BaseGraph.

    Arguments:
        edges: set of edge tuples (i, j)
    """
    super().__init__(edges)

all_node_pairs: set property

Return a list of all possible node pairs in the graph.

coords: dict property writable

Return the dictionary of node coordinates.

edge_weights: dict property writable

Return the dictionary of edge weights.

has_coords: bool property

Check if the graph has coordinates.

Requires all nodes to have coordinates.

has_edge_weights: bool property

Check if the graph has edge weights.

Requires all edges to have a weight.

has_edges: bool property

Check if the graph has edges.

has_node_weights: bool property

Check if the graph has node weights.

Requires all nodes to have a weight.

node_weights: dict property writable

Return the dictionary of node weights.

sorted_edges: set property

Returns the set of edges (u, v) such that (u < v).

circle(n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0)) -> DataGraph classmethod

Constructs a circle graph, with the respective coordinates.

Parameters:

  • n
    (int) –

    number of nodes.

  • spacing
    (float, default: 1.0 ) –

    distance between each node.

  • center
    (tuple, default: (0.0, 0.0) ) –

    point (x, y) to set as the center of the graph.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def circle(
    cls,
    n: int,
    spacing: float = 1.0,
    center: tuple = (0.0, 0.0),
) -> DataGraph:
    """Constructs a circle graph, with the respective coordinates.

    Arguments:
        n: number of nodes.
        spacing: distance between each node.
        center: point (x, y) to set as the center of the graph.
    """

    d_theta = (2.0 * np.pi) / n
    r = spacing / (2.0 * np.sin(np.pi / n))
    theta = np.linspace(0.0, 2.0 * np.pi - d_theta, n)
    coords = [
        (x + center[0], y + center[1]) for x, y in zip(r * np.cos(theta), r * np.sin(theta))
    ]
    edges = [(i, i + 1) for i in range(n - 1)] + [(n - 1, 0)]
    graph = cls.from_coordinates(coords)
    graph.add_edges_from(edges)
    graph._reset_dicts()
    return graph

distances(edge_list: Iterable | None = None) -> dict

Returns a dictionary of distances for a given set of edges.

Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.

Parameters:

  • edge_list
    (Iterable | None, default: None ) –

    set of edges.

Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict:
    """Returns a dictionary of distances for a given set of edges.

    Distances are calculated directly from the coordinates. Raises an error
    if there are no coordinates on the graph.

    Arguments:
        edge_list: set of edges.
    """
    if self.has_coords:
        if edge_list is None:
            edge_list = self.all_node_pairs
        elif len(edge_list) == 0:  # type: ignore [arg-type]
            raise ValueError("Trying to compute distances for an empty edge list.")
        return distances(self.coords, edge_list)
    else:
        raise AttributeError("Trying to compute distances for a graph without coordinates.")

draw(ax: Axes | None = None, **kwargs: Any) -> None

Draw the graph.

Uses the draw_networkx function from NetworkX.

Parameters:

  • ax
    (Axes | None, default: None ) –

    Axes object to draw on. If None, uses the current Axes.

  • **kwargs
    (Any, default: {} ) –

    keyword-arguments to pass to draw_networkx.

Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None:
    """Draw the graph.

    Uses the draw_networkx function from NetworkX.

    Args:
        ax: Axes object to draw on. If None, uses the current Axes.
        **kwargs: keyword-arguments to pass to draw_networkx.
    """
    if self.has_coords:
        if "hide_ticks" not in kwargs:
            kwargs["hide_ticks"] = False

        nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)

        if ax is None:
            ax = plt.gca()
        ax.set_xlabel("x")
        ax.set_ylabel("y")
        ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)

        # minimum ybox
        ylim = ax.get_ylim()
        if (ylim[1] - ylim[0]) < 2:
            y_center = (ylim[0] + ylim[1]) / 2
            ax.set_ylim(y_center - 1, y_center + 1)
        plt.tight_layout()
    else:
        nx.draw_networkx(self, ax=ax, **kwargs)

from_coordinates(coords: list | dict) -> BaseGraph classmethod

Construct a base graph from a set of coordinates.

Parameters:

  • coords
    (list | dict) –

    list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_coordinates(cls, coords: list | dict) -> BaseGraph:
    """Construct a base graph from a set of coordinates.

    Arguments:
        coords: list or dictionary of coordinate pairs.
    """
    if isinstance(coords, list):
        nodes = list(range(len(coords)))
        coords_dict = {i: pos for i, pos in enumerate(coords)}
    elif isinstance(coords, dict):
        nodes = list(coords.keys())
        coords_dict = coords
    graph = cls.from_nodes(nodes)
    graph._coords = coords_dict
    graph._reset_dicts()
    return graph

from_matrix(data: ArrayLike) -> DataGraph classmethod

Constructs a graph from a symmetric square matrix.

The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.

Parameters:

  • data
    (ArrayLike) –

    symmetric square matrix.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def from_matrix(cls, data: ArrayLike) -> DataGraph:
    """Constructs a graph from a symmetric square matrix.

    The diagonal values are set as the node weights. For each entry (i, j)
    where M[i, j] != 0 an edge (i, j) is added to the graph and the value
    M[i, j] is set as its weight.

    Arguments:
        data: symmetric square matrix.
    """
    if data.ndim != 2:
        raise ValueError("2D Matrix required.")
    if not np.allclose(data, data.T, rtol=0.0, atol=ATOL_32):
        raise ValueError("Matrix must be symmetric.")

    diag = np.diag(data)
    n_nodes = len(diag)
    node_weights = {i: diag[i] for i in range(n_nodes)}
    if np.allclose(diag, np.zeros(n_nodes), rtol=0.0, atol=ATOL_32):
        node_weights = {i: None for i in range(n_nodes)}
    else:
        node_weights = {i: diag[i].item() for i in range(n_nodes)}

    data[data <= ATOL_32] = 0.0
    non_zero = data.nonzero()
    i_list = non_zero[0].tolist()
    j_list = non_zero[1].tolist()

    edge_list = [(i, j) for i, j in zip(i_list, j_list) if i < j]
    edge_weights = {(i, j): data[i, j].item() for i, j in edge_list}

    graph = cls.from_nodes(range(n_nodes))
    graph.add_edges_from(edge_list)
    graph.node_weights = node_weights
    graph.edge_weights = edge_weights
    return graph

from_nodes(nodes: Iterable) -> BaseGraph classmethod

Construct a base graph from a set of nodes.

Parameters:

  • nodes
    (Iterable) –

    set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nodes(cls, nodes: Iterable) -> BaseGraph:
    """Construct a base graph from a set of nodes.

    Arguments:
        nodes: set of nodes.
    """
    graph = cls()
    graph.add_nodes_from(nodes)
    graph._coords = {i: None for i in graph.nodes}
    graph._reset_dicts()
    return graph

from_nx(g: nx.Graph) -> BaseGraph classmethod

Convert a NetworkX Graph object into a QoolQit graph instance.

The input networkx.Graph graph must be defined only with the following allowed

Node attributes

pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers. weight: represents the node weight. Must be a real number.

Edge attributes: weight: represents the edge weight. Must be a real number.

Returns an instance of the class with following attributes
  • _node_weights : dict[node, float or None]
  • _edge_weights : dict[(u,v), float or None]
  • _coords : dict[node, (float,float) or None]
Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nx(cls, g: nx.Graph) -> BaseGraph:
    """Convert a NetworkX Graph object into a QoolQit graph instance.

    The input `networkx.Graph` graph must be defined only with the following allowed

    Node attributes:
        pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers.
        weight: represents the node weight. Must be a real number.
    Edge attributes:
        weight: represents the edge weight. Must be a real number.

    Returns an instance of the class with following attributes:
        - _node_weights : dict[node, float or None]
        - _edge_weights : dict[(u,v), float or None]
        - _coords       : dict[node, (float,float) or None]
    """
    if not isinstance(g, nx.Graph):
        raise TypeError("Input must be a networkx.Graph instance.")

    g = nx.convert_node_labels_to_integers(g)
    num_nodes = len(g.nodes)
    num_edges = len(g.edges)

    # validate node attributes
    for name, data in g.nodes.data():
        unexpected_keys = set(data) - {"weight", "pos"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in node attributes.")

    node_pos = nx.get_node_attributes(g, "pos")
    if node_pos:
        if len(node_pos) != num_nodes:
            raise ValueError("Node attribute `pos` must be defined for all nodes")
        for name, pos in node_pos.items():
            is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2)
            is_real = all(isinstance(p, (float, int)) for p in pos)
            if not (is_2D & is_real):
                raise TypeError(
                    f"In node {name} the `pos` attribute must be a 2D tuple/list"
                    f" of real numbers, got {pos} instead."
                )
    node_weights = nx.get_node_attributes(g, "weight")
    if node_weights:
        if len(node_weights) != num_nodes:
            raise ValueError("Node attribute `weight` must be defined for all nodes")
        for name, weight in node_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In node {name} the `weight` attribute must be a real number, "
                    f"got {type(weight)} instead."
                    ""
                )

    # validate edge attributes
    for u, v, data in g.edges.data():
        unexpected_keys = set(data) - {"weight"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in edge attributes.")
    edge_weights = nx.get_edge_attributes(g, "weight")
    if edge_weights:
        if len(edge_weights) != num_edges:
            raise ValueError("Edge attribute `weight` must be defined for all edges")
        for name, weight in edge_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In edge {name}, the attribute `weight` must be a real number, "
                    f"got {type(weight)} instead."
                )

    # build the instance of the graph
    graph = cls()
    graph.add_nodes_from(g.nodes)
    graph.add_edges_from(g.edges)
    graph._node_weights = nx.get_node_attributes(g, "weight", default=None)
    graph._coords = nx.get_node_attributes(g, "pos", default=None)
    graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)

    return graph

from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph classmethod

Convert a PyTorch Geometric Data object into a DataGraph instance.

Requires torch_geometric. Uses to_networkx internally.

Default attributes copied (if present on data ):

  • Node: x, pos (pos is also stored in _coords)
  • Edge: edge_attr
  • Graph: y

Use node_attrs, edge_attrs, graph_attrs for extras.

QoolQit weights (_node_weights, _edge_weights) are not populated automatically — use the explicit parameters:

  • node_weights_attr: real-valued tensor of shape (N,) or (N, 1). Defaults to None.
  • edge_weights_attr: real-valued tensor of shape (E,) or (E, 1) where E = edge_index.shape[1] (directed count). Defaults to None.

The weight attribute is also stored as a regular node/edge attribute.

Parameters:

  • data
    (Data) –

    PyTorch Geometric Data object to convert.

  • node_attrs
    (Iterable[str] | None, default: None ) –

    extra node attributes to copy (beyond x and pos).

  • edge_attrs
    (Iterable[str] | None, default: None ) –

    extra edge attributes to copy (beyond edge_attr).

  • graph_attrs
    (Iterable[str] | None, default: None ) –

    extra graph-level attributes to copy (beyond y).

  • node_weights_attr
    (str | None, default: None ) –

    Data attribute to use as node weights.

  • edge_weights_attr
    (str | None, default: None ) –

    Data attribute to use as edge weights.

Returns:

  • DataGraph

    DataGraph with _coords, _node_weights, _edge_weights

  • DataGraph

    populated where applicable.

Raises:

  • ImportError

    if torch_geometric is not installed.

  • TypeError

    if data is not a torch_geometric.data.Data instance, or if a weight attribute is not a torch.Tensor.

  • AttributeError

    if a specified weight attribute is missing.

  • ValueError

    if a weight tensor has an incompatible shape or size.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def from_pyg(
    cls,
    data: torch_geometric.data.Data,
    node_attrs: Iterable[str] | None = None,
    edge_attrs: Iterable[str] | None = None,
    graph_attrs: Iterable[str] | None = None,
    node_weights_attr: str | None = None,
    edge_weights_attr: str | None = None,
) -> DataGraph:
    """Convert a PyTorch Geometric Data object into a DataGraph instance.

    Requires ``torch_geometric``. Uses ``to_networkx`` internally.

    **Default attributes copied (if present on** ``data`` **):**

    - Node: ``x``, ``pos`` (``pos`` is also stored in ``_coords``)
    - Edge: ``edge_attr``
    - Graph: ``y``

    Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.

    **QoolQit weights** (``_node_weights``, ``_edge_weights``) are not
    populated automatically — use the explicit parameters:

    - ``node_weights_attr``: real-valued tensor of shape ``(N,)`` or
      ``(N, 1)``. Defaults to ``None``.
    - ``edge_weights_attr``: real-valued tensor of shape ``(E,)`` or
      ``(E, 1)`` where ``E = edge_index.shape[1]`` (directed count).
      Defaults to ``None``.

    The weight attribute is also stored as a regular node/edge attribute.

    Arguments:
        data: PyTorch Geometric Data object to convert.
        node_attrs: extra node attributes to copy (beyond x and pos).
        edge_attrs: extra edge attributes to copy (beyond edge_attr).
        graph_attrs: extra graph-level attributes to copy (beyond y).
        node_weights_attr: Data attribute to use as node weights.
        edge_weights_attr: Data attribute to use as edge weights.

    Returns:
        DataGraph with ``_coords``, ``_node_weights``, ``_edge_weights``
        populated where applicable.

    Raises:
        ImportError: if ``torch_geometric`` is not installed.
        TypeError: if ``data`` is not a ``torch_geometric.data.Data``
            instance, or if a weight attribute is not a ``torch.Tensor``.
        AttributeError: if a specified weight attribute is missing.
        ValueError: if a weight tensor has an incompatible shape or size.
    """
    try:
        from torch_geometric.data import Data
        from torch_geometric.utils import to_networkx
    except ImportError as e:
        raise ImportError("Please, install the `torch_geometric` package.") from e

    if not isinstance(data, Data):
        raise TypeError("Input must be a torch_geometric.data.Data object.")

    # Validate weight attrs early and keep the squeezed tensors
    node_tensor = (
        cls._validate_weights_attr(data, node_weights_attr, data.num_nodes, "node")
        if node_weights_attr is not None
        else None
    )
    edge_tensor = (
        cls._validate_weights_attr(data, edge_weights_attr, data.num_edges, "edge")
        if edge_weights_attr is not None
        else None
    )

    # Select unique attributes and add default ones only if present in the data
    node_attrs_set = {k for k in {"x", "pos"} if k in data}
    if node_attrs is not None:
        node_attrs_set |= set(node_attrs)
    if node_weights_attr is not None:
        node_attrs_set.add(node_weights_attr)

    edge_attrs_set = {k for k in {"edge_attr"} if k in data}
    if edge_attrs is not None:
        edge_attrs_set |= set(edge_attrs)
    if edge_weights_attr is not None:
        edge_attrs_set.add(edge_weights_attr)

    graph_attrs_set = {k for k in {"y"} if k in data}
    if graph_attrs is not None:
        graph_attrs_set |= set(graph_attrs)

    # Convert to NetworkX (undirected, no self-loops)
    nx_graph = to_networkx(
        data,
        node_attrs=list(node_attrs_set),
        edge_attrs=list(edge_attrs_set),
        graph_attrs=list(graph_attrs_set),
        to_undirected=True,
        remove_self_loops=True,
    )

    # Build the DataGraph: edges carry their data, nodes carry their data
    graph = cls(nx_graph.edges(data=True))
    graph.add_nodes_from(nx_graph.nodes(data=True))
    graph.graph = nx_graph.graph

    # Re-initialize QoolQit internal dicts for all nodes/edges
    graph._coords = {n: None for n in graph.nodes}
    graph._reset_dicts()

    # pos → _coords (stored as list [x, y] by to_networkx)
    for node, node_data in nx_graph.nodes(data=True):
        if "pos" in node_data:
            graph._coords[node] = tuple(node_data["pos"])  # type: ignore[assignment]

    # node_weights_attr → _node_weights
    if node_tensor is not None:
        for i in range(data.num_nodes):
            graph._node_weights[i] = node_tensor[i].item()

    # edge_weights_attr → _edge_weights
    if edge_tensor is not None:
        seen: set = set()
        for idx in range(data.edge_index.shape[1]):
            u = int(data.edge_index[0, idx].item())
            v = int(data.edge_index[1, idx].item())
            key = (min(u, v), max(u, v))
            if key not in seen:
                graph._edge_weights[key] = edge_tensor[idx].item()
                seen.add(key)

    return graph

heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a heavy-hexagonal lattice graph, with respective coordinates.

Parameters:

  • m
    (int) –

    Number of rows of hexagons.

  • n
    (int) –

    Number of columns of hexagons.

  • spacing
    (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Notes

The heavy-hexagonal lattice is a regular hexagonal lattice where each edge is decorated with an additional lattice site.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def heavy_hexagonal(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a heavy-hexagonal lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of hexagons.
        n: Number of columns of hexagons.
        spacing: The distance between adjacent nodes on the final lattice.

    Notes:
        The heavy-hexagonal lattice is a regular hexagonal lattice where
        each edge is decorated with an additional lattice site.
    """
    G_hex = nx.hexagonal_lattice_graph(m, n, with_positions=True)
    pos_unit = nx.get_node_attributes(G_hex, "pos")

    G_heavy = nx.Graph()
    scaling_factor = 2 * spacing

    label_map = {}
    for old_label, (x, y) in pos_unit.items():
        # Relabel to an even-integer grid to make space for midpoint nodes
        new_label = (2 * old_label[0], 2 * old_label[1])
        label_map[old_label] = new_label

        # Scale positions and add the node to the new graph
        new_pos = (x * scaling_factor, y * scaling_factor)
        G_heavy.add_node(new_label, pos=new_pos)

    for u_old, v_old in G_hex.edges():
        u_new, v_new = label_map[u_old], label_map[v_old]

        mid_label = ((u_new[0] + v_new[0]) // 2, (u_new[1] + v_new[1]) // 2)

        pos_u = G_heavy.nodes[u_new]["pos"]
        pos_v = G_heavy.nodes[v_new]["pos"]
        mid_pos = ((pos_u[0] + pos_v[0]) / 2, (pos_u[1] + pos_v[1]) / 2)

        G_heavy.add_node(mid_label, pos=mid_pos)
        G_heavy.add_edge(u_new, mid_label)
        G_heavy.add_edge(mid_label, v_new)

    final_nodes = sorted(list(G_heavy.nodes()))

    final_coords = [G_heavy.nodes[label]["pos"] for label in final_nodes]

    label_to_int = {label: i for i, label in enumerate(final_nodes)}

    final_edges = [(label_to_int[u], label_to_int[v]) for u, v in G_heavy.edges()]

    graph = cls.from_coordinates(final_coords)
    graph.add_edges_from(final_edges)
    graph._reset_dicts()
    return graph

hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a hexagonal lattice graph, with respective coordinates.

Parameters:

  • m
    (int) –

    Number of rows of hexagons.

  • n
    (int) –

    Number of columns of hexagons.

  • spacing
    (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def hexagonal(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a hexagonal lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of hexagons.
        n: Number of columns of hexagons.
        spacing: The distance between adjacent nodes on the final lattice.
    """
    G = nx.hexagonal_lattice_graph(m, n, with_positions=True)
    G = nx.convert_node_labels_to_integers(G)
    pos_unit = nx.get_node_attributes(G, "pos")

    final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}

    graph = cls.from_coordinates(final_pos)
    graph.add_edges_from(G.edges)
    graph._reset_dicts()
    return graph

interactions() -> dict

Rydberg model interaction 1/r^6 between pair of nodes.

Source code in qoolqit/graphs/base_graph.py
def interactions(self) -> dict:
    """Rydberg model interaction 1/r^6 between pair of nodes."""
    return {p: 1.0 / (r**6) for p, r in self.distances().items()}

is_ud_graph() -> bool

Check if the graph is unit-disk.

Source code in qoolqit/graphs/base_graph.py
def is_ud_graph(self) -> bool:
    """Check if the graph is unit-disk."""
    try:
        self.ud_radius_range()
        return True
    except ValueError:
        return False

line(n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a line graph, with the respective coordinates.

Parameters:

  • n
    (int) –

    number of nodes.

  • spacing
    (float, default: 1.0 ) –

    distance between each node.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def line(cls, n: int, spacing: float = 1.0) -> DataGraph:
    """Constructs a line graph, with the respective coordinates.

    Arguments:
        n: number of nodes.
        spacing: distance between each node.
    """
    coords = [(i * spacing, 0.0) for i in range(n)]
    graph = cls.from_coordinates(coords)
    edges = [(i, i + 1) for i in range(0, n - 1)]
    graph.add_edges_from(edges)
    graph._reset_dicts()
    return graph

max_distance(connected: bool | None = None) -> float

Returns the maximum distance in the graph.

Parameters:

  • connected
    (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float:
    """Returns the maximum distance in the graph.

    Arguments:
        connected: if True/False, computes only over connected/disconnected nodes.
    """
    distance: float
    if connected is None:
        distance = max(self.distances(self.all_node_pairs).values())
    elif connected:
        distance = max(self.distances(self.sorted_edges).values())
    else:
        distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values())
    return distance

min_distance(connected: bool | None = None) -> float

Returns the minimum distance in the graph.

Parameters:

  • connected
    (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float:
    """Returns the minimum distance in the graph.

    Arguments:
        connected: if True/False, computes only over connected/disconnected nodes.
    """
    distance: float
    if connected is None:
        distance = min(self.distances(self.all_node_pairs).values())
    elif connected:
        distance = min(self.distances(self.sorted_edges).values())
    else:
        distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values())
    return distance

random_er(n: int, p: float, seed: int | None = None) -> DataGraph classmethod

Constructs an Erdős–Rényi random graph.

Parameters:

  • n
    (int) –

    number of nodes.

  • p
    (float) –

    probability that any two nodes connect.

  • seed
    (int | None, default: None ) –

    random seed.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def random_er(cls, n: int, p: float, seed: int | None = None) -> DataGraph:
    """Constructs an Erdős–Rényi random graph.

    Arguments:
        n: number of nodes.
        p: probability that any two nodes connect.
        seed: random seed.
    """
    base_graph = nx.erdos_renyi_graph(n, p, seed)
    graph = DataGraph.from_nodes(list(base_graph.nodes))
    graph.add_edges_from(base_graph.edges)
    graph._reset_dicts()
    return graph

random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph classmethod

Constructs a random unit-disk graph.

The nodes are sampled uniformly from a square of size (L x L). If L is not given, it is estimated based on a rough heuristic that of packing N nodes on a square of side L such that the expected minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).

Parameters:

  • n
    (int) –

    number of nodes.

  • radius
    (float, default: 1.0 ) –

    radius to use for defining the unit-disk edges.

  • L
    (float | None, default: None ) –

    size of the square on which to sample the node coordinates.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def random_ud(
    cls,
    n: int,
    radius: float = 1.0,
    L: float | None = None,
) -> DataGraph:
    """Constructs a random unit-disk graph.

    The nodes are sampled uniformly from a square of size (L x L).
    If L is not given, it is estimated based on a rough heuristic that
    of packing N nodes on a square of side L such that the expected
    minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).

    Arguments:
        n: number of nodes.
        radius: radius to use for defining the unit-disk edges.
        L: size of the square on which to sample the node coordinates.
    """
    if L is None:
        L = (radius / 2) * ((np.pi * n) ** 0.5)
    coords = random_coords(n, L)
    graph = cls.from_coordinates(coords)
    edges = graph.ud_edges(radius)
    graph.add_edges_from(edges)
    graph._reset_dicts()
    return graph

rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

  • scaling
    (float | None, default: None ) –

    value to scale by.

  • spacing
    (float | None, default: None ) –

    value to set as the minimum distance in the graph.

Source code in qoolqit/graphs/base_graph.py
def rescale_coords(
    self,
    *args: Any,
    scaling: float | None = None,
    spacing: float | None = None,
) -> None:
    """Rescales the node coordinates by a factor.

    Accepts either a scaling or a spacing factor.

    Arguments:
        scaling: value to scale by.
        spacing: value to set as the minimum distance in the graph.
    """
    if self.has_coords:
        msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument."
        if (len(args) > 0) or (scaling is None and spacing is None):
            raise TypeError(msg)
        if scaling is None and spacing is not None:
            self._coords = space_coords(self._coords, spacing)
        elif spacing is None and scaling is not None:
            self._coords = scale_coords(self._coords, scaling)
        else:
            raise TypeError(msg)
    else:
        raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")

set_ud_edges(radius: float) -> None

Reset the set of edges to be equal to the set of unit-disk edges.

Source code in qoolqit/graphs/data_graph.py
def set_ud_edges(self, radius: float) -> None:
    """Reset the set of edges to be equal to the set of unit-disk edges."""
    super().set_ud_edges(radius=radius)
    self._edge_weights = {e: None for e in self.sorted_edges}

square(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a square lattice graph, with respective coordinates.

Parameters:

  • m
    (int) –

    Number of rows of square.

  • n
    (int) –

    Number of columns of square.

  • spacing
    (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def square(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a square lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of square.
        n: Number of columns of square.
        spacing: The distance between adjacent nodes on the final lattice.
    """
    G = nx.grid_2d_graph(m, n)
    final_coords = [(x * spacing, y * spacing) for (x, y) in list(G.nodes)]
    G = nx.convert_node_labels_to_integers(G)

    graph = DataGraph.from_coordinates(final_coords)
    graph.add_edges_from(G.edges)
    graph._reset_dicts()
    return graph

to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data

Convert the DataGraph to a PyTorch Geometric Data object.

Requires torch_geometric. Uses from_networkx internally.

Default attributes exported (if present on the graph):

  • Node "x"data.x; Edge "edge_attr"data.edge_attr
  • Graph "y"data.y

Use node_attrs, edge_attrs, graph_attrs for extras.

QoolQit internal dicts exported when populated:

  • _coordsdata.pos (float64, shape (N, 2))
  • _node_weightsdata.<node_weights_attr> (float64, shape (N,)). Defaults to "weight".
  • _edge_weightsdata.<edge_weights_attr> (float64, shape (2*E,)). Defaults to "edge_weight".

Parameters:

  • node_attrs
    (Iterable[str] | None, default: None ) –

    extra node attributes to export (beyond x).

  • edge_attrs
    (Iterable[str] | None, default: None ) –

    extra edge attributes to export (beyond edge_attr).

  • graph_attrs
    (Iterable[str] | None, default: None ) –

    extra graph-level attributes to export (beyond y).

  • node_weights_attr
    (str, default: 'weight' ) –

    Data attribute name for node weights. Defaults to "weight".

  • edge_weights_attr
    (str, default: 'edge_weight' ) –

    Data attribute name for edge weights. Defaults to "edge_weight".

Returns:

  • Data

    PyTorch Geometric Data object.

Raises:

  • ImportError

    if torch_geometric is not installed.

Source code in qoolqit/graphs/data_graph.py
def to_pyg(
    self,
    node_attrs: Iterable[str] | None = None,
    edge_attrs: Iterable[str] | None = None,
    graph_attrs: Iterable[str] | None = None,
    node_weights_attr: str = "weight",
    edge_weights_attr: str = "edge_weight",
) -> torch_geometric.data.Data:
    """Convert the DataGraph to a PyTorch Geometric Data object.

    Requires ``torch_geometric``. Uses ``from_networkx`` internally.

    **Default attributes exported (if present on the graph):**

    - Node ``"x"`` → ``data.x``; Edge ``"edge_attr"`` → ``data.edge_attr``
    - Graph ``"y"`` → ``data.y``

    Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.

    **QoolQit internal dicts exported when populated:**

    - ``_coords`` → ``data.pos`` (float64, shape ``(N, 2)``)
    - ``_node_weights`` → ``data.<node_weights_attr>`` (float64, shape
      ``(N,)``). Defaults to ``"weight"``.
    - ``_edge_weights`` → ``data.<edge_weights_attr>`` (float64, shape
      ``(2*E,)``). Defaults to ``"edge_weight"``.

    Arguments:
        node_attrs: extra node attributes to export (beyond x).
        edge_attrs: extra edge attributes to export (beyond edge_attr).
        graph_attrs: extra graph-level attributes to export (beyond y).
        node_weights_attr: Data attribute name for node weights.
            Defaults to ``"weight"``.
        edge_weights_attr: Data attribute name for edge weights.
            Defaults to ``"edge_weight"``.

    Returns:
        PyTorch Geometric Data object.

    Raises:
        ImportError: if ``torch_geometric`` is not installed.
    """
    try:
        import torch
        from torch_geometric.utils import from_networkx
    except ImportError as e:
        raise ImportError("Please, install the `torch_geometric` package.") from e

    node_attrs_set = set(node_attrs) if node_attrs else set()
    edge_attrs_set = set(edge_attrs) if edge_attrs else set()
    graph_attrs_list = list(graph_attrs) if graph_attrs else []

    # Add default PyG attributes if present in the graph
    if any("x" in d for _, d in self.nodes(data=True)):
        node_attrs_set.add("x")
    if any("edge_attr" in d for _, _, d in self.edges(data=True)):
        edge_attrs_set.add("edge_attr")
    if "y" in self.graph:
        graph_attrs_list.append("y")

    # Build a filtered copy with only the requested attributes
    filtered_graph = nx.Graph()
    filtered_graph.add_nodes_from(self.nodes())
    filtered_graph.add_edges_from(self.edges())

    for node, node_data in self.nodes(data=True):
        for key, value in node_data.items():
            if key in node_attrs_set:
                filtered_graph.nodes[node][key] = value
    for u, v, edge_data in self.edges(data=True):
        for key, value in edge_data.items():
            if key in edge_attrs_set:
                filtered_graph.edges[u, v][key] = value
    for attr in graph_attrs_list:
        if attr in self.graph:
            filtered_graph.graph[attr] = self.graph[attr]

    data = from_networkx(filtered_graph)

    # Export _coords → pos
    if self.has_coords:
        positions = [self._coords[n] for n in sorted(self.nodes())]
        data.pos = torch.tensor(positions, dtype=torch.float64)

    # Export _node_weights → node_weights_attr
    if self.has_node_weights:
        weights = [self._node_weights[n] for n in sorted(self.nodes())]
        setattr(data, node_weights_attr, torch.tensor(weights, dtype=torch.float64))

    # Export _edge_weights → edge_weights_attr (one value per directed edge in edge_index)
    if self.has_edge_weights:
        edge_weights: list[float] = []
        for i in range(data.edge_index.shape[1]):
            u, v = int(data.edge_index[0, i].item()), int(data.edge_index[1, i].item())
            edge_key = (min(u, v), max(u, v))
            edge_weights.append(float(self._edge_weights[edge_key]))  # type: ignore[arg-type]
        setattr(data, edge_weights_attr, torch.tensor(edge_weights, dtype=torch.float64))

    return data

triangular(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a triangular lattice graph, with respective coordinates.

Parameters:

  • m
    (int) –

    Number of rows of triangles.

  • n
    (int) –

    Number of columns of triangles.

  • spacing
    (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def triangular(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a triangular lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of triangles.
        n: Number of columns of triangles.
        spacing: The distance between adjacent nodes on the final lattice.
    """
    G = nx.triangular_lattice_graph(m, n, with_positions=True)
    G = nx.convert_node_labels_to_integers(G)
    pos_unit = nx.get_node_attributes(G, "pos")

    final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}

    graph = cls.from_coordinates(final_pos)
    graph.add_edges_from(G.edges)
    graph._reset_dicts()
    return graph

ud_edges(radius: float) -> set

Returns the set of edges given by the intersection of circles of a given radius.

Parameters:

  • radius
    (float) –

    the value

Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set:
    """Returns the set of edges given by the intersection of circles of a given radius.

    Arguments:
        radius: the value
    """
    if self.has_coords:
        return set(e for e, d in self.distances().items() if less_or_equal(d, radius))
    else:
        raise AttributeError("Getting unit disk edges is not valid without coordinates.")

ud_radius_range() -> tuple

Return the range (R_min, R_max) where the graph is unit-disk.

The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:

graph.ud_edges(radius = R) == graph.sorted edges

Source code in qoolqit/graphs/base_graph.py
def ud_radius_range(self) -> tuple:
    """Return the range (R_min, R_max) where the graph is unit-disk.

    The graph is unit-disk if the maximum distance between all connected nodes is
    smaller than the minimum distance between disconnected nodes. This means that
    for any value R in that interval, the following condition is true:

    graph.ud_edges(radius = R) == graph.sorted edges
    """
    if self.has_coords:
        n_edges = len(self.sorted_edges)
        if n_edges == 0:
            # If the graph is empty and has coordinates
            return (0.0, self.min_distance(connected=False))
        elif n_edges == len(self.all_node_pairs):
            # If the graph is fully connected
            return (self.max_distance(connected=True), float("inf"))
        elif self.max_distance(connected=True) < self.min_distance(connected=False):
            return (self.max_distance(connected=True), self.min_distance(connected=False))
        else:
            raise ValueError("Graph is not unit disk.")
    else:
        raise AttributeError("Checking if graph is unit disk is not valid without coordinates.")

Delay(duration: float, *args: float, **kwargs: float | np.ndarray)

An empty waveform.

Parameters:

  • duration

    (float) –

    the total duration of the waveform.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/base_waveforms.py
def __init__(
    self,
    duration: float,
    *args: float,
    **kwargs: float | np.ndarray,
) -> None:
    """Initializes the Waveform.

    Arguments:
        duration: the total duration of the waveform.
    """

    if duration <= 0:
        raise ValueError("Duration needs to be a positive non-zero value.")

    if len(args) > 0:
        raise ValueError(
            f"Extra arguments in {type(self).__name__} need to be passed as keyword arguments"
        )

    self._duration = duration
    self._params_dict = kwargs

    self._max: float | None = None
    self._min: float | None = None

    for key, value in kwargs.items():
        setattr(self, key, value)

duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

Device(pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None)

QoolQit Device wrapper around a Pulser BaseDevice.

Parameters:

  • pulser_device

    (BaseDevice) –

    a BaseDevice to build the QoolQit device from.

  • default_converter

    (Optional[UnitConverter], default: None ) –

    optional unit converter to handle unit conversion.

Examples:

From Pulser device:

qoolqit_device = Device(pulser_device=pulser_device)

From remote Pulser device:

from pulser_pasqal import PasqalCloud
from qoolqit import Device

# Fetch the remote device from the connection
connection = PasqalCloud()
pulser_fresnel_device = connection.fetch_available_devices()["FRESNEL"]

# Wrap a Pulser device object into a QoolQit Device
fresnel_device = Device(pulser_device=PulserFresnelDevice)

From custom Pulser device:

from dataclasses import replace
from pulser import AnalogDevice
from qoolqit import Device

# Converting the pulser Device object in a VirtualDevice object
VirtualAnalog = AnalogDevice.to_virtual()
# Replacing desired values
ModdedAnalogDevice = replace(
    VirtualAnalog,
    max_radial_distance=100,
    max_sequence_duration=7000
    )

# Wrap a Pulser device object into a QoolQit Device
mod_analog_device = Device(pulser_device=ModdedAnalogDevice)

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(
    self,
    pulser_device: BaseDevice,
    default_converter: Optional[UnitConverter] = None,
) -> None:

    if not isinstance(pulser_device, BaseDevice):
        raise TypeError("`pulser_device` must be an instance of Pulser BaseDevice class.")

    # Store it for all subsequent lookups
    self._pulser_device: BaseDevice = pulser_device
    self._name: str = self._pulser_device.name

    # Physical constants / channel & limit lookups (assumes 'rydberg_global' channel)
    self._C6 = self._pulser_device.interaction_coeff
    self._clock_period = self._pulser_device.channels["rydberg_global"].clock_period
    # Relevant limits from the underlying device (float or None)
    self._max_duration = self._pulser_device.max_sequence_duration
    self._max_amp = self._pulser_device.channels["rydberg_global"].max_amp
    self._upper_amp = self._max_amp or 4 * math.pi
    self._max_abs_det = self._pulser_device.channels["rydberg_global"].max_abs_detuning
    self._min_distance = self._pulser_device.min_atom_distance
    self._lower_distance = self._min_distance or 5.0
    self._max_radial_distance = self._pulser_device.max_radial_distance

    # ratio between maximum amplitude and maximum interaction energy J_max = C6/r_min^6
    self._energy_ratio: float = (self._upper_amp * self._lower_distance**6) / self._C6

    # layouts
    self._requires_layout = self._pulser_device.requires_layout

    if default_converter is not None:
        # Snapshot the caller-provided factors so reset() reproduces them exactly.
        t0, e0, d0 = default_converter.factors
        self._default_factory: Callable[[], UnitConverter] = lambda: UnitConverter(
            self._C6, t0, e0, d0
        )
    else:
        self._default_factory = lambda: UnitConverter.from_distance(
            self._C6, self._lower_distance
        )

    self.reset_converter()

specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection
    (RemoteConnection) –

    connection object to fetch the available devices.

  • name
    (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)

info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)

reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter

set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)

set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)

DigitalAnalogDevice()

A device with digital and analog capabilities.

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(self) -> None:
    super().__init__(pulser_device=pulser.DigitalAnalogDevice)

specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection
    (RemoteConnection) –

    connection object to fetch the available devices.

  • name
    (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)

info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)

reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter

set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)

set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)

Drive(*args: Any, amplitude: Waveform | None = None, detuning: Waveform | None = None, weighted_detunings: list[WeightedDetuning] | None = None, phase: float = 0.0)

The drive Hamiltonian acting over a duration.

Must be instantiated with keyword arguments. Accepts either an amplitude waveform, a detuning waveform, or both. A phase value can also be passed.

Parameters:

  • amplitude

    (Waveform | None, default: None ) –

    waveform representing Ω(t) in the drive Hamiltonian.

  • detuning

    (Waveform | None, default: None ) –

    waveform representing δ(t) in the drive Hamiltonian.

  • phase

    (float, default: 0.0 ) –

    phase value ɸ for the amplitude term.

  • weighted_detunings

    (list[WeightedDetuning] | None, default: None ) –

    additional waveforms and weights applied to individual qubits. Note that these detunings are not supported on all devices.

Attributes:

Source code in qoolqit/drive.py
def __init__(
    self,
    *args: Any,
    amplitude: Waveform | None = None,
    detuning: Waveform | None = None,
    weighted_detunings: list[WeightedDetuning] | None = None,
    phase: float = 0.0,
) -> None:
    """Default constructor for the Drive.

    Must be instantiated with keyword arguments. Accepts either an amplitude waveform,
    a detuning waveform, or both. A phase value can also be passed.

    Arguments:
        amplitude: waveform representing Ω(t) in the drive Hamiltonian.
        detuning: waveform representing δ(t) in the drive Hamiltonian.
        phase: phase value ɸ for the amplitude term.
        weighted_detunings: additional waveforms and weights applied to individual
            qubits. Note that these detunings are not supported on all devices.
    """

    if len(args) > 0:
        raise TypeError("Please pass the `amplitude` and / or `detuning` as keyword arguments.")

    if amplitude is None and detuning is None:
        raise ValueError("Amplitude and detuning cannot both be empty.")

    for arg in [amplitude, detuning]:
        if arg is not None and not isinstance(arg, Waveform):
            raise TypeError("Amplitude and detuning must be of type Waveform.")

    self._amplitude: Waveform
    self._detuning: Waveform
    self._amplitude_orig: Waveform
    self._detuning_orig: Waveform

    if amplitude is None and isinstance(detuning, Waveform):
        self._amplitude = Delay(detuning.duration)
        self._detuning = detuning
    elif detuning is None and isinstance(amplitude, Waveform):
        self._amplitude = amplitude
        self._detuning = Delay(amplitude.duration)
    elif isinstance(detuning, Waveform) and isinstance(amplitude, Waveform):
        self._amplitude = amplitude
        self._detuning = detuning

    if self._amplitude.min() < 0.0:
        raise ValueError("Amplitude cannot be negative.")

    self._amplitude_orig = self._amplitude
    self._detuning_orig = self._detuning

    if self._amplitude.duration > self._detuning.duration:
        extra_duration = self._amplitude.duration - self._detuning.duration
        self._detuning = CompositeWaveform(self._detuning, Delay(extra_duration))
    elif self._detuning.duration > self._amplitude.duration:
        extra_duration = self._detuning.duration - self._amplitude.duration
        self._amplitude = CompositeWaveform(self._amplitude, Delay(extra_duration))

    self._duration = self._amplitude.duration
    self._phase = phase
    self._weighted_detunings = weighted_detunings if weighted_detunings is not None else []

amplitude: Waveform property

The amplitude waveform in the drive.

detuning: Waveform property

The detuning waveform in the drive.

phase: float property

The phase value in the drive.

weighted_detunings: Sequence[WeightedDetuning] property

Detunings applied to individual qubits.

Interpolated(duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = 'PchipInterpolator', **interpolator_kwargs: Any)

A waveform created from interpolation of a set of data points.

Parameters:

  • duration

    (int) –

    The waveform duration (in ns).

  • values

    (ArrayLike) –

    Values of the interpolation points. Must be a list of castable to float or a parametrized object.

  • times

    (ArrayLike, default: None ) –

    Fractions of the total duration (between 0 and 1), indicating where to place each value on the time axis. Must be a list of castable to float or a parametrized object. If not given, the values are spread evenly throughout the full duration of the waveform.

  • interpolator

    (str, default: 'PchipInterpolator' ) –

    The SciPy interpolation class to use. Supports "PchipInterpolator" and "interp1d".

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    values: ArrayLike,
    times: Optional[ArrayLike] = None,
    interpolator: str = "PchipInterpolator",
    **interpolator_kwargs: Any,
):
    """Initializes a new Interpolated waveform."""
    super().__init__(duration)
    self._values = np.array(values, dtype=float)
    if times:  # fractional times in [0,1]
        if any([(ft < 0) or (ft > 1) for ft in times]):
            raise ValueError("All values in `times` must be in [0,1].")
        self._times = np.array(times, dtype=float)
        if len(times) != len(self._values):
            raise ValueError(
                "Arguments `values` and `times` must be arrays of the same length."
            )
    else:
        self._times = np.linspace(0, 1, num=len(self._values))

    if interpolator not in self._valid_interpolators:
        raise ValueError(
            f"Invalid interpolator '{interpolator}', only "
            "accepts: " + ", ".join(self._valid_interpolators)
        )
    self._interpolator = interpolator
    self._interpolator_kwargs = interpolator_kwargs

    interp_cls = getattr(interpolate, interpolator)
    self._interp_func = interp_cls(duration * self._times, self._values, **interpolator_kwargs)

duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

MockDevice()

A virtual device for unconstrained prototyping.

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(self) -> None:
    super().__init__(pulser_device=pulser.MockDevice)

specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection
    (RemoteConnection) –

    connection object to fetch the available devices.

  • name
    (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)

info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)

reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter

set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)

set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)

PiecewiseLinear(durations: list | tuple, values: list | tuple)

A piecewise linear waveform.

Creates a composite waveform of N ramps that linearly interpolate through the given N+1 values.

Parameters:

  • durations

    (list | tuple) –

    list or tuple of N duration values.

  • values

    (list | tuple) –

    list or tuple of N+1 waveform values.

Methods:

  • function

    Identifies the right waveform in the composition and evaluates it at time t.

  • max

    Get the maximum value of the waveform.

  • min

    Get the approximate minimum value of the waveform.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • durations (list[float]) –

    Returns the list of durations of each individual waveform.

  • n_waveforms (int) –

    Returns the number of waveforms.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

  • times (list[float]) –

    Returns the list of times when each individual waveform starts.

  • waveforms (list[Waveform]) –

    Returns a list of the individual waveforms.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    durations: list | tuple,
    values: list | tuple,
) -> None:
    if not (isinstance(durations, (list, tuple)) or isinstance(values, (list, tuple))):
        raise TypeError(
            "A PiecewiseLinear waveform requires a list or tuple of durations and values."
        )

    if len(durations) + 1 != len(values) or len(durations) == 1:
        raise ValueError(
            "A PiecewiseLinear waveform requires N durations and N + 1 values, for N >= 2."
        )

    for duration in durations:
        if duration == 0.0:
            raise ValueError("A PiecewiseLinear interval cannot have zero duration.")

    self.values = values

    wfs = [Ramp(dur, values[i], values[i + 1]) for i, dur in enumerate(durations)]

    super().__init__(*wfs)

duration: float property

Returns the duration of the waveform.

durations: list[float] property

Returns the list of durations of each individual waveform.

n_waveforms: int property

Returns the number of waveforms.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

times: list[float] property

Returns the list of times when each individual waveform starts.

waveforms: list[Waveform] property

Returns a list of the individual waveforms.

function(t: float) -> float

Identifies the right waveform in the composition and evaluates it at time t.

Source code in qoolqit/waveforms/base_waveforms.py
def function(self, t: float) -> float:
    """Identifies the right waveform in the composition and evaluates it at time t."""
    idx = np.searchsorted(self.times, t, side="right") - 1
    if idx == -1:
        return 0.0
    if idx == self.n_waveforms:
        if t == self.times[-1]:
            idx = idx - 1
        else:
            return 0.0

    local_t = t - self.times[idx]
    value: float = self.waveforms[idx](local_t)
    return value

max() -> float

Get the maximum value of the waveform.

Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float:
    """Get the maximum value of the waveform."""
    return max([wf.max() for wf in self.waveforms])

min() -> float

Get the approximate minimum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float:
    """Get the approximate minimum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the minimum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._min is None:
        self._approximate_min_max()
    return cast(float, self._min)

QuantumProgram(register: Register, drive: Drive)

A program representing a Sequence acting on a Register of qubits.

Parameters:

  • register

    (Register) –

    the register of qubits, defining their positions.

  • drive

    (Drive) –

    the drive acting on qubits, defining amplitude, detuning and phase.

Methods:

  • compile_to

    Compiles the quantum program for execution on a specific device.

Attributes:

Source code in qoolqit/program.py
def __init__(
    self,
    register: Register,
    drive: Drive,
) -> None:

    if not isinstance(register, Register):
        raise TypeError("`register` must be of type Register.")
    self._register = register
    if not isinstance(drive, Drive):
        raise TypeError("`drive` must be of type Drive.")
    self._drive = drive
    self._compiled_sequence: PulserSequence | None = None
    for detuning in drive.weighted_detunings:
        for key in detuning.weights.keys():
            if key not in register.qubits:
                raise ValueError(
                    "In this QuantumProgram, the drive and the register "
                    f"do not match: qubit {key} appears in the drive but "
                    "is not defined in the register."
                )

compiled_sequence: PulserSequence property

The Pulser sequence compiled to a specific device.

drive: Drive property

The driving waveforms.

is_compiled: bool property

Check if the program has been compiled.

register: Register property

The register of qubits.

compile_to(device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None) -> None

Compiles the quantum program for execution on a specific device.

The compilation process adapts the program to the device's constraints while preserving the relative ratios of the original program parameters. Different compilation profiles optimize for specific objectives:

  • CompilerProfile.MAX_ENERGY (default): Scales the program to utilize the device's maximum capabilities. The drive amplitude and the register positions are rescaled to achieve respectively the maximum amplitude and the minimum pairwise distance compatible with the input program and the device's constraints.
  • CompilerProfile.WORKING_POINT: .

Further options DO NOT preserve the input program, but rather adapts the program to the device's constraint. Programs compiled this way are not guaranteed to be portable across devices.

  • device_max_duration_ratio: Rescale the drive duration to a fraction of the device's maximum allowed duration. This option is useful in adiabatic protocols where one simply seek to minimize the time derivative of the drive's amplitude.

Parameters:

  • device
    (Device) –

    The target device for compilation.

  • profile
    (CompilerProfile, default: MAX_ENERGY ) –

    The compilation strategy to optimize the program. Defaults to CompilerProfile.MAX_ENERGY.

  • device_max_duration_ratio
    (float | None, default: None ) –

    Whether to set the program duration to a fraction of the device's maximum allowed duration. Must be a number in the range (0, 1]. Can only be set if the device has a maximum allowed duration.

Raises:

  • CompilationError

    If the compilation fails due to device constraints.

Source code in qoolqit/program.py
def compile_to(
    self,
    device: Device,
    profile: CompilerProfile = CompilerProfile.MAX_ENERGY,
    device_max_duration_ratio: float | None = None,
) -> None:
    """Compiles the quantum program for execution on a specific device.

    The compilation process adapts the program to the device's constraints while
    preserving the relative ratios of the original program parameters. Different
    compilation profiles optimize for specific objectives:

    - CompilerProfile.MAX_ENERGY (default): Scales the program to utilize the device's
        maximum capabilities. The drive amplitude and the register positions are rescaled
        to achieve respectively the maximum amplitude and the minimum pairwise distance
        compatible with the input program and the device's constraints.
    - CompilerProfile.WORKING_POINT: .

    Further options DO NOT preserve the input program, but rather adapts the program to
    the device's constraint. Programs compiled this way are not guaranteed to be portable
    across devices.

    - device_max_duration_ratio: Rescale the drive duration to a fraction of the
        device's maximum allowed duration.
        This option is useful in adiabatic protocols where one simply seek to
        minimize the time derivative of the drive's amplitude.

    Args:
        device: The target device for compilation.
        profile: The compilation strategy to optimize the program.
            Defaults to CompilerProfile.MAX_ENERGY.
        device_max_duration_ratio: Whether to set the program duration to a fraction of
            the device's maximum allowed duration. Must be a number in the range (0, 1].
            Can only be set if the device has a maximum allowed duration.

    Raises:
        CompilationError: If the compilation fails due to device constraints.
    """

    if device_max_duration_ratio is not None:
        if device._max_duration is None:
            raise ValueError(
                "Cannot set `device_max_duration_ratio` because the target device "
                "does not have a maximum allowed duration."
            )
        if not (0 < device_max_duration_ratio <= 1):
            raise ValueError(
                "`device_max_duration_ratio` must be between 0 and 1, "
                f"got {device_max_duration_ratio} instead."
            )

    compiler = SequenceCompiler(
        self.register, self.drive, device, profile, device_max_duration_ratio
    )
    self._device = device
    self._compiled_sequence = compiler.compile_sequence()

Ramp(duration: float, initial_value: float, final_value: float)

A ramp that linearly interpolates between an initial and final value.

Parameters:

  • duration

    (float) –

    the total duration.

  • initial_value

    (float) –

    the initial value at t = 0.

  • final_value

    (float) –

    the final value at t = duration.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    initial_value: float,
    final_value: float,
) -> None:
    super().__init__(duration, initial_value=initial_value, final_value=final_value)

duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

Register(qubits: dict)

The Register in QoolQit, representing a set of qubits with coordinates.

Parameters:

  • qubits

    (dict) –

    a dictionary of qubits and respective coordinates {q: (x, y), ...}.

Methods:

Attributes:

  • n_qubits (int) –

    Number of qubits in the Register.

  • qubits (dict) –

    Returns the dictionary of qubits and respective coordinates.

  • qubits_ids (list) –

    Returns the qubit keys.

Source code in qoolqit/register.py
def __init__(self, qubits: dict) -> None:
    """Default constructor for the Register.

    Arguments:
        qubits: a dictionary of qubits and respective coordinates {q: (x, y), ...}.
    """
    if not isinstance(qubits, dict):
        raise TypeError(
            "Register must be initialized with a dictionary of "
            "qubits and respective coordinates {q: (x, y), ...}."
        )

    self._qubits: dict = qubits

n_qubits: int property

Number of qubits in the Register.

qubits: dict property

Returns the dictionary of qubits and respective coordinates.

qubits_ids: list property

Returns the qubit keys.

distances() -> dict

Distance between each qubit pair.

Source code in qoolqit/register.py
def distances(self) -> dict:
    """Distance between each qubit pair."""
    pairs = all_node_pairs(list(self.qubits.keys()))
    return distances(self.qubits, pairs)

draw(return_fig: bool = False) -> Figure | None

Draw the register.

Parameters:

  • return_fig
    (bool, default: False ) –

    boolean argument to return the matplotlib figure.

Source code in qoolqit/register.py
def draw(self, return_fig: bool = False) -> Figure | None:
    """Draw the register.

    Arguments:
        return_fig: boolean argument to return the matplotlib figure.
    """
    fig, ax = plt.subplots(1, 1, figsize=(4, 4), dpi=150)
    ax.set_aspect("equal")

    x_coords, y_coords = zip(*self.qubits.values())
    x_min, x_max = min(x_coords), max(x_coords)
    y_min, y_max = min(y_coords), max(y_coords)

    grid_x_min, grid_x_max = min(-1, x_min), max(1, x_max)
    grid_y_min, grid_y_max = min(-1, y_min), max(1, y_max)

    grid_scale = ceil(max(grid_x_max - grid_x_min, grid_y_max - grid_y_min))

    ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)
    ax.set_axisbelow(True)
    ax.set_xlabel("x")
    ax.set_ylabel("y")

    eps = 0.05 * grid_scale
    ax.set_xlim(grid_x_min - eps, grid_x_max + eps)
    ax.set_ylim(grid_y_min - eps, grid_y_max + eps)

    possible_multiples = [0.2, 0.25, 0.5, 1.0, 2.0, 2.5, 5.0, 10.0]
    grid_multiple = min(possible_multiples, key=lambda x: abs(x - grid_scale / 8))
    majorLocatorX = MultipleLocator(grid_multiple)
    majorLocatorY = MultipleLocator(grid_multiple)
    ax.xaxis.set_major_locator(majorLocatorX)
    ax.yaxis.set_major_locator(majorLocatorY)

    ax.scatter(x_coords, y_coords, s=50, color="darkgreen")

    ax.tick_params(axis="both", which="both", labelbottom=True, labelleft=True, labelsize=8)

    if return_fig:
        plt.close()
        return fig
    else:
        return None

from_coordinates(coords: list) -> Register classmethod

Initializes a Register from a list of coordinates.

Parameters:

  • coords
    (list) –

    a list of coordinates [(x, y), ...]

Source code in qoolqit/register.py
@classmethod
def from_coordinates(cls, coords: list) -> Register:
    """Initializes a Register from a list of coordinates.

    Arguments:
        coords: a list of coordinates [(x, y), ...]
    """
    if not isinstance(coords, list):
        raise TypeError(
            "Register must be initialized with a dictionary of qubit and coordinates."
        )
    coords_dict = {i: pos for i, pos in enumerate(coords)}
    return cls(coords_dict)

from_graph(graph: DataGraph) -> Register classmethod

Initializes a Register from a graph that has coordinates.

Parameters:

  • graph
    (DataGraph) –

    a DataGraph instance.

Source code in qoolqit/register.py
@classmethod
def from_graph(cls, graph: DataGraph) -> Register:
    """Initializes a Register from a graph that has coordinates.

    Arguments:
        graph: a DataGraph instance.
    """

    if not graph.has_coords:
        raise ValueError("Initializing a register from a graph requires node coordinates.")

    if len(graph.nodes) == 0:
        raise ValueError("Trying to initialize a register from an empty graph.")

    return cls(graph.coords)

interactions() -> dict

Interaction 1/r^6 between each qubit pair.

Source code in qoolqit/register.py
def interactions(self) -> dict:
    """Interaction 1/r^6 between each qubit pair."""
    return {p: 1.0 / (r**6) for p, r in self.distances().items()}

max_radial_distance() -> float

Maximum radial distance between all qubits.

Source code in qoolqit/register.py
def max_radial_distance(self) -> float:
    """Maximum radial distance between all qubits."""
    max_radial_distance: float = max(self.radial_distances().values())
    return max_radial_distance

min_distance() -> float

Minimum distance between all qubit pairs.

Source code in qoolqit/register.py
def min_distance(self) -> float:
    """Minimum distance between all qubit pairs."""
    distance: float = min(self.distances().values())
    return distance

radial_distances() -> dict

Radial distance of each qubit from the origin.

Source code in qoolqit/register.py
def radial_distances(self) -> dict:
    """Radial distance of each qubit from the origin."""
    return radial_distances(self.qubits)

SequenceCompiler(register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None)

Compiles a QoolQit Register and Drive to a Device.

Parameters:

  • register

    (Register) –

    the QoolQit Register.

  • drive

    (Drive) –

    the QoolQit Drive.

  • device

    (Device) –

    the QoolQit Device.

  • profile

    (CompilerProfile) –

    the CompilerProfile to use.

  • device_max_duration_ratio

    (float | None, default: None ) –

    optionally set the program duration to a fraction of the device's maximum allowed duration.

Source code in qoolqit/execution/sequence_compiler.py
def __init__(
    self,
    register: Register,
    drive: Drive,
    device: Device,
    profile: CompilerProfile,
    device_max_duration_ratio: float | None = None,
) -> None:
    """Initializes the compiler.

    Args:
        register: the QoolQit Register.
        drive: the QoolQit Drive.
        device: the QoolQit Device.
        profile: the CompilerProfile to use.
        device_max_duration_ratio: optionally set the program duration to a fraction
            of the device's maximum allowed duration.
    """

    self._register = register
    self._drive = drive
    self._device = device
    self._target_device = device._device
    self._profile = profile
    self._device_max_duration_ratio = device_max_duration_ratio
    self._compilation_function: Callable = basic_compilation

Sin(duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0)

An arbitrary sine over a given duration.

Parameters:

  • duration

    (float) –

    the total duration.

  • amplitude

    (float, default: 1.0 ) –

    the amplitude of the sine wave.

  • omega

    (float, default: 1.0 ) –

    the frequency of the sine wave.

  • phi

    (float, default: 0.0 ) –

    the phase of the sine wave.

  • shift

    (float, default: 0.0 ) –

    the vertical shift of the sine wave.

Methods:

  • max

    Get the approximate maximum value of the waveform.

  • min

    Get the approximate minimum value of the waveform.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    amplitude: float = 1.0,
    omega: float = 1.0,
    phi: float = 0.0,
    shift: float = 0.0,
) -> None:
    super().__init__(duration, amplitude=amplitude, omega=omega, phi=phi, shift=shift)

duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

max() -> float

Get the approximate maximum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float:
    """Get the approximate maximum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the maximum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._max is None:
        self._approximate_min_max()
    return cast(float, self._max)

min() -> float

Get the approximate minimum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float:
    """Get the approximate minimum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the minimum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._min is None:
        self._approximate_min_max()
    return cast(float, self._min)

available_default_devices() -> None

Show the default available devices in QooQit.

Source code in qoolqit/devices/device.py
def available_default_devices() -> None:
    """Show the default available devices in QooQit."""
    for dev in (AnalogDevice(), DigitalAnalogDevice(), MockDevice()):
        dev.info()

devices

Modules:

Classes:

  • AnalogDevice

    A realistic device for analog sequence execution.

  • Device

    QoolQit Device wrapper around a Pulser BaseDevice.

  • DigitalAnalogDevice

    A device with digital and analog capabilities.

  • MockDevice

    A virtual device for unconstrained prototyping.

Functions:

AnalogDevice()

A realistic device for analog sequence execution.

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(self) -> None:
    super().__init__(pulser_device=pulser.AnalogDevice)
specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection
    (RemoteConnection) –

    connection object to fetch the available devices.

  • name
    (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)
info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)
reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter
set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)

Device(pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None)

QoolQit Device wrapper around a Pulser BaseDevice.

Parameters:

  • pulser_device
    (BaseDevice) –

    a BaseDevice to build the QoolQit device from.

  • default_converter
    (Optional[UnitConverter], default: None ) –

    optional unit converter to handle unit conversion.

Examples:

From Pulser device:

qoolqit_device = Device(pulser_device=pulser_device)

From remote Pulser device:

from pulser_pasqal import PasqalCloud
from qoolqit import Device

# Fetch the remote device from the connection
connection = PasqalCloud()
pulser_fresnel_device = connection.fetch_available_devices()["FRESNEL"]

# Wrap a Pulser device object into a QoolQit Device
fresnel_device = Device(pulser_device=PulserFresnelDevice)

From custom Pulser device:

from dataclasses import replace
from pulser import AnalogDevice
from qoolqit import Device

# Converting the pulser Device object in a VirtualDevice object
VirtualAnalog = AnalogDevice.to_virtual()
# Replacing desired values
ModdedAnalogDevice = replace(
    VirtualAnalog,
    max_radial_distance=100,
    max_sequence_duration=7000
    )

# Wrap a Pulser device object into a QoolQit Device
mod_analog_device = Device(pulser_device=ModdedAnalogDevice)

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(
    self,
    pulser_device: BaseDevice,
    default_converter: Optional[UnitConverter] = None,
) -> None:

    if not isinstance(pulser_device, BaseDevice):
        raise TypeError("`pulser_device` must be an instance of Pulser BaseDevice class.")

    # Store it for all subsequent lookups
    self._pulser_device: BaseDevice = pulser_device
    self._name: str = self._pulser_device.name

    # Physical constants / channel & limit lookups (assumes 'rydberg_global' channel)
    self._C6 = self._pulser_device.interaction_coeff
    self._clock_period = self._pulser_device.channels["rydberg_global"].clock_period
    # Relevant limits from the underlying device (float or None)
    self._max_duration = self._pulser_device.max_sequence_duration
    self._max_amp = self._pulser_device.channels["rydberg_global"].max_amp
    self._upper_amp = self._max_amp or 4 * math.pi
    self._max_abs_det = self._pulser_device.channels["rydberg_global"].max_abs_detuning
    self._min_distance = self._pulser_device.min_atom_distance
    self._lower_distance = self._min_distance or 5.0
    self._max_radial_distance = self._pulser_device.max_radial_distance

    # ratio between maximum amplitude and maximum interaction energy J_max = C6/r_min^6
    self._energy_ratio: float = (self._upper_amp * self._lower_distance**6) / self._C6

    # layouts
    self._requires_layout = self._pulser_device.requires_layout

    if default_converter is not None:
        # Snapshot the caller-provided factors so reset() reproduces them exactly.
        t0, e0, d0 = default_converter.factors
        self._default_factory: Callable[[], UnitConverter] = lambda: UnitConverter(
            self._C6, t0, e0, d0
        )
    else:
        self._default_factory = lambda: UnitConverter.from_distance(
            self._C6, self._lower_distance
        )

    self.reset_converter()
specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection
    (RemoteConnection) –

    connection object to fetch the available devices.

  • name
    (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)
info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)
reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter
set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)

DigitalAnalogDevice()

A device with digital and analog capabilities.

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(self) -> None:
    super().__init__(pulser_device=pulser.DigitalAnalogDevice)
specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection
    (RemoteConnection) –

    connection object to fetch the available devices.

  • name
    (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)
info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)
reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter
set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)

MockDevice()

A virtual device for unconstrained prototyping.

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(self) -> None:
    super().__init__(pulser_device=pulser.MockDevice)
specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection
    (RemoteConnection) –

    connection object to fetch the available devices.

  • name
    (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)
info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)
reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter
set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)

available_default_devices() -> None

Show the default available devices in QooQit.

Source code in qoolqit/devices/device.py
def available_default_devices() -> None:
    """Show the default available devices in QooQit."""
    for dev in (AnalogDevice(), DigitalAnalogDevice(), MockDevice()):
        dev.info()

device

Classes:

  • AnalogDevice

    A realistic device for analog sequence execution.

  • Device

    QoolQit Device wrapper around a Pulser BaseDevice.

  • DigitalAnalogDevice

    A device with digital and analog capabilities.

  • MockDevice

    A virtual device for unconstrained prototyping.

Functions:

AnalogDevice()

A realistic device for analog sequence execution.

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(self) -> None:
    super().__init__(pulser_device=pulser.AnalogDevice)
specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection (RemoteConnection) –

    connection object to fetch the available devices.

  • name (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)
info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)
reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter
set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)
Device(pulser_device: BaseDevice, default_converter: Optional[UnitConverter] = None)

QoolQit Device wrapper around a Pulser BaseDevice.

Parameters:

  • pulser_device
    (BaseDevice) –

    a BaseDevice to build the QoolQit device from.

  • default_converter
    (Optional[UnitConverter], default: None ) –

    optional unit converter to handle unit conversion.

Examples:

From Pulser device:

qoolqit_device = Device(pulser_device=pulser_device)

From remote Pulser device:

from pulser_pasqal import PasqalCloud
from qoolqit import Device

# Fetch the remote device from the connection
connection = PasqalCloud()
pulser_fresnel_device = connection.fetch_available_devices()["FRESNEL"]

# Wrap a Pulser device object into a QoolQit Device
fresnel_device = Device(pulser_device=PulserFresnelDevice)

From custom Pulser device:

from dataclasses import replace
from pulser import AnalogDevice
from qoolqit import Device

# Converting the pulser Device object in a VirtualDevice object
VirtualAnalog = AnalogDevice.to_virtual()
# Replacing desired values
ModdedAnalogDevice = replace(
    VirtualAnalog,
    max_radial_distance=100,
    max_sequence_duration=7000
    )

# Wrap a Pulser device object into a QoolQit Device
mod_analog_device = Device(pulser_device=ModdedAnalogDevice)

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(
    self,
    pulser_device: BaseDevice,
    default_converter: Optional[UnitConverter] = None,
) -> None:

    if not isinstance(pulser_device, BaseDevice):
        raise TypeError("`pulser_device` must be an instance of Pulser BaseDevice class.")

    # Store it for all subsequent lookups
    self._pulser_device: BaseDevice = pulser_device
    self._name: str = self._pulser_device.name

    # Physical constants / channel & limit lookups (assumes 'rydberg_global' channel)
    self._C6 = self._pulser_device.interaction_coeff
    self._clock_period = self._pulser_device.channels["rydberg_global"].clock_period
    # Relevant limits from the underlying device (float or None)
    self._max_duration = self._pulser_device.max_sequence_duration
    self._max_amp = self._pulser_device.channels["rydberg_global"].max_amp
    self._upper_amp = self._max_amp or 4 * math.pi
    self._max_abs_det = self._pulser_device.channels["rydberg_global"].max_abs_detuning
    self._min_distance = self._pulser_device.min_atom_distance
    self._lower_distance = self._min_distance or 5.0
    self._max_radial_distance = self._pulser_device.max_radial_distance

    # ratio between maximum amplitude and maximum interaction energy J_max = C6/r_min^6
    self._energy_ratio: float = (self._upper_amp * self._lower_distance**6) / self._C6

    # layouts
    self._requires_layout = self._pulser_device.requires_layout

    if default_converter is not None:
        # Snapshot the caller-provided factors so reset() reproduces them exactly.
        t0, e0, d0 = default_converter.factors
        self._default_factory: Callable[[], UnitConverter] = lambda: UnitConverter(
            self._C6, t0, e0, d0
        )
    else:
        self._default_factory = lambda: UnitConverter.from_distance(
            self._C6, self._lower_distance
        )

    self.reset_converter()
specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection (RemoteConnection) –

    connection object to fetch the available devices.

  • name (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)
info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)
reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter
set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)
DigitalAnalogDevice()

A device with digital and analog capabilities.

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(self) -> None:
    super().__init__(pulser_device=pulser.DigitalAnalogDevice)
specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection (RemoteConnection) –

    connection object to fetch the available devices.

  • name (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)
info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)
reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter
set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)
MockDevice()

A virtual device for unconstrained prototyping.

Methods:

  • from_connection

    Return the specified device from the selected device from a connection.

  • info

    Show the device short description and constraints.

  • reset_converter

    Resets the unit converter to the default one.

  • set_distance_unit

    Changes the unit converter according to a reference distance unit.

  • set_energy_unit

    Changes the unit converter according to a reference energy unit.

Attributes:

  • specs (dict[str, float | None]) –

    Return the device specification constraints.

Source code in qoolqit/devices/device.py
def __init__(self) -> None:
    super().__init__(pulser_device=pulser.MockDevice)
specs: dict[str, float | None] property

Return the device specification constraints.

from_connection(connection: RemoteConnection, name: str) -> Device classmethod

Return the specified device from the selected device from a connection.

Available devices through the provided connection are can be seen with the connection.fetch_available_devices() method.

Parameters:

  • connection (RemoteConnection) –

    connection object to fetch the available devices.

  • name (str) –

    The name of the desired device.

Returns:

  • Device ( Device ) –

    The requested device.

Raises:

  • ValueError

    If the requested device is not available through the provided connection.

Example:

from pulser_pasqal import PasqalCloud
fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")

Source code in qoolqit/devices/device.py
@classmethod
def from_connection(cls, connection: RemoteConnection, name: str) -> Device:
    """Return the specified device from the selected device from a connection.

    Available devices through the provided connection are can be seen with
    the `connection.fetch_available_devices()` method.

    Args:
        connection (RemoteConnection): connection object to fetch the available devices.
        name (str): The name of the desired device.

    Returns:
        Device: The requested device.

    Raises:
        ValueError: If the requested device is not available through the provided connection.

    Example:
    ```python
    from pulser_pasqal import PasqalCloud
    fresnel_device = Device.from_connection(connection=PasqalCloud(), name="FRESNEL")
    ```
    """
    available_remote_devices = connection.fetch_available_devices()
    if name not in available_remote_devices:
        raise ValueError(f"Device {name} is not available through the provided connection.")
    pulser_device = available_remote_devices[name]
    return cls(pulser_device=pulser_device)
info() -> None

Show the device short description and constraints.

Source code in qoolqit/devices/device.py
def info(self) -> None:
    """Show the device short description and constraints."""
    print(self)
reset_converter() -> None

Resets the unit converter to the default one.

Source code in qoolqit/devices/device.py
def reset_converter(self) -> None:
    """Resets the unit converter to the default one."""
    # Create a NEW converter so mutations don't persist.
    self._converter = self._default_converter
set_distance_unit(distance: float) -> None

Changes the unit converter according to a reference distance unit.

Source code in qoolqit/devices/device.py
def set_distance_unit(self, distance: float) -> None:
    """Changes the unit converter according to a reference distance unit."""
    self.converter.factors = self.converter.factors_from_distance(distance)
set_energy_unit(energy: float) -> None

Changes the unit converter according to a reference energy unit.

Source code in qoolqit/devices/device.py
def set_energy_unit(self, energy: float) -> None:
    """Changes the unit converter according to a reference energy unit."""
    self.converter.factors = self.converter.factors_from_energy(energy)
available_default_devices() -> None

Show the default available devices in QooQit.

Source code in qoolqit/devices/device.py
def available_default_devices() -> None:
    """Show the default available devices in QooQit."""
    for dev in (AnalogDevice(), DigitalAnalogDevice(), MockDevice()):
        dev.info()

unit_converter

Classes:

  • UnitConverter

    A dataclass representing a unit converter in the Rydberg-Analog model.

UnitConverter(C6: float, time: float, energy: float, distance: float) dataclass

A dataclass representing a unit converter in the Rydberg-Analog model.

Includes three inter-dependent factors for TIME, ENERGY and DISTANCE conversion, also depending on the interaction coefficient C6. The converter checks the following invariants, based on the units used by Pulser:

Conversion invariants: 1. TIME * ENERGY = 1000 ( <=> TIME = 1000 / ENERGY ) 2. DISTANCE^6 * ENERGY = C6 ( <=> ENERGY = C6 / (DISTANCE ^ 6) )

Methods:

  • factors_from_distance

    Get factors from a different reference distance than the one set.

  • factors_from_energy

    Get factors from a different reference energy than the one set.

  • factors_from_time

    Get factors from a different reference time than the one set.

  • from_distance

    Instantiate from a reference C6 value and a reference distance unit.

  • from_energy

    Instantiate from a reference C6 value and a reference energy unit.

  • from_time

    Instantiate from a reference C6 value and a reference time unit.

  • validate_factors

    Returns True if the conversion invariants are respected.

Attributes:

  • C6 (float) –

    Time conversion factor.

  • energy (float) –

    Distance conversion factor.

  • factors (tuple[float, ...]) –

    Return the current conversion factors set.

  • time (float) –

    Energy conversion factor.

C6: float = field(repr=False) class-attribute instance-attribute

Time conversion factor.

energy: float instance-attribute

Distance conversion factor.

factors: tuple[float, ...] property writable

Return the current conversion factors set.

time: float instance-attribute

Energy conversion factor.

factors_from_distance(distance: float) -> tuple[float, ...]

Get factors from a different reference distance than the one set.

Source code in qoolqit/devices/unit_converter.py
def factors_from_distance(self, distance: float) -> tuple[float, ...]:
    """Get factors from a different reference distance than the one set."""
    return _factors_from_distance(self.C6, distance)
factors_from_energy(energy: float) -> tuple[float, ...]

Get factors from a different reference energy than the one set.

Source code in qoolqit/devices/unit_converter.py
def factors_from_energy(self, energy: float) -> tuple[float, ...]:
    """Get factors from a different reference energy than the one set."""
    return _factors_from_energy(self.C6, energy)
factors_from_time(time: float) -> tuple[float, ...]

Get factors from a different reference time than the one set.

Source code in qoolqit/devices/unit_converter.py
def factors_from_time(self, time: float) -> tuple[float, ...]:
    """Get factors from a different reference time than the one set."""
    return _factors_from_time(self.C6, time)
from_distance(C6: float, distance: float) -> UnitConverter classmethod

Instantiate from a reference C6 value and a reference distance unit.

Source code in qoolqit/devices/unit_converter.py
@classmethod
def from_distance(cls, C6: float, distance: float) -> UnitConverter:
    """Instantiate from a reference C6 value and a reference distance unit."""
    time, energy, distance = _factors_from_distance(C6, distance)
    return UnitConverter(C6, time, energy, distance)
from_energy(C6: float, energy: float) -> UnitConverter classmethod

Instantiate from a reference C6 value and a reference energy unit.

Source code in qoolqit/devices/unit_converter.py
@classmethod
def from_energy(cls, C6: float, energy: float) -> UnitConverter:
    """Instantiate from a reference C6 value and a reference energy unit."""
    time, energy, distance = _factors_from_energy(C6, energy)
    return UnitConverter(C6, time, energy, distance)
from_time(C6: float, time: float) -> UnitConverter classmethod

Instantiate from a reference C6 value and a reference time unit.

Source code in qoolqit/devices/unit_converter.py
@classmethod
def from_time(cls, C6: float, time: float) -> UnitConverter:
    """Instantiate from a reference C6 value and a reference time unit."""
    time, energy, distance = _factors_from_time(C6, time)
    return UnitConverter(C6, time, energy, distance)
validate_factors(time: float, energy: float, distance: float) -> bool

Returns True if the conversion invariants are respected.

Source code in qoolqit/devices/unit_converter.py
def validate_factors(self, time: float, energy: float, distance: float) -> bool:
    """Returns True if the conversion invariants are respected."""
    time_energy_inv = time * energy
    energy_dist_inv = (distance**6) * energy
    return isclose(time_energy_inv, 1000.0) and isclose(energy_dist_inv, self.C6)

drive

Classes:

Drive(*args: Any, amplitude: Waveform | None = None, detuning: Waveform | None = None, weighted_detunings: list[WeightedDetuning] | None = None, phase: float = 0.0)

The drive Hamiltonian acting over a duration.

Must be instantiated with keyword arguments. Accepts either an amplitude waveform, a detuning waveform, or both. A phase value can also be passed.

Parameters:

  • amplitude
    (Waveform | None, default: None ) –

    waveform representing Ω(t) in the drive Hamiltonian.

  • detuning
    (Waveform | None, default: None ) –

    waveform representing δ(t) in the drive Hamiltonian.

  • phase
    (float, default: 0.0 ) –

    phase value ɸ for the amplitude term.

  • weighted_detunings
    (list[WeightedDetuning] | None, default: None ) –

    additional waveforms and weights applied to individual qubits. Note that these detunings are not supported on all devices.

Attributes:

Source code in qoolqit/drive.py
def __init__(
    self,
    *args: Any,
    amplitude: Waveform | None = None,
    detuning: Waveform | None = None,
    weighted_detunings: list[WeightedDetuning] | None = None,
    phase: float = 0.0,
) -> None:
    """Default constructor for the Drive.

    Must be instantiated with keyword arguments. Accepts either an amplitude waveform,
    a detuning waveform, or both. A phase value can also be passed.

    Arguments:
        amplitude: waveform representing Ω(t) in the drive Hamiltonian.
        detuning: waveform representing δ(t) in the drive Hamiltonian.
        phase: phase value ɸ for the amplitude term.
        weighted_detunings: additional waveforms and weights applied to individual
            qubits. Note that these detunings are not supported on all devices.
    """

    if len(args) > 0:
        raise TypeError("Please pass the `amplitude` and / or `detuning` as keyword arguments.")

    if amplitude is None and detuning is None:
        raise ValueError("Amplitude and detuning cannot both be empty.")

    for arg in [amplitude, detuning]:
        if arg is not None and not isinstance(arg, Waveform):
            raise TypeError("Amplitude and detuning must be of type Waveform.")

    self._amplitude: Waveform
    self._detuning: Waveform
    self._amplitude_orig: Waveform
    self._detuning_orig: Waveform

    if amplitude is None and isinstance(detuning, Waveform):
        self._amplitude = Delay(detuning.duration)
        self._detuning = detuning
    elif detuning is None and isinstance(amplitude, Waveform):
        self._amplitude = amplitude
        self._detuning = Delay(amplitude.duration)
    elif isinstance(detuning, Waveform) and isinstance(amplitude, Waveform):
        self._amplitude = amplitude
        self._detuning = detuning

    if self._amplitude.min() < 0.0:
        raise ValueError("Amplitude cannot be negative.")

    self._amplitude_orig = self._amplitude
    self._detuning_orig = self._detuning

    if self._amplitude.duration > self._detuning.duration:
        extra_duration = self._amplitude.duration - self._detuning.duration
        self._detuning = CompositeWaveform(self._detuning, Delay(extra_duration))
    elif self._detuning.duration > self._amplitude.duration:
        extra_duration = self._detuning.duration - self._amplitude.duration
        self._amplitude = CompositeWaveform(self._amplitude, Delay(extra_duration))

    self._duration = self._amplitude.duration
    self._phase = phase
    self._weighted_detunings = weighted_detunings if weighted_detunings is not None else []
amplitude: Waveform property

The amplitude waveform in the drive.

detuning: Waveform property

The detuning waveform in the drive.

phase: float property

The phase value in the drive.

weighted_detunings: Sequence[WeightedDetuning] property

Detunings applied to individual qubits.

WeightedDetuning(weights: dict[Any, float], waveform: Waveform) dataclass

A weighted detuning.

See https://pasqal-io.github.io/qoolqit/latest/theory/rydberg_model/#weighted-detuning for details on weighted detunings.

Note: detuning with positive waveforms cannot be instantiated.

Attributes:

  • waveform (Waveform) –

    The waveform for this detuning.

  • weights (dict[Any, float]) –

    Association of weights to qubits.

waveform: Waveform instance-attribute

The waveform for this detuning.

In the companion documentation, this is the function Delta(t).

weights: dict[Any, float] instance-attribute

Association of weights to qubits.

Each weight must be in [0, 1], where 0 means that the waveform is ignored for this qubit and 1 means that the waveform is fully applied to this qubit.

In the companion documentation, these are the value epsilon_i.

embedding

Collection of graph and matrix embedding algorithms.

Modules:

Classes:

BaseEmbedder(algorithm: Callable, config: ConfigType)

Abstract base class for all embedders.

An embedder is a function that maps a InDataType to an OutDataType through an embedding algorithm. Parameters of the embedding algorithm can be customized through the EmbedderConfig.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

  • algorithm
    (Callable) –

    a callable to the algorithm function.

  • config
    (ConfigType) –

    a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

  • validate_input

    Checks if the given data is compatible with the embedder.

  • validate_output

    Checks if the resulting output is expected by the embedder.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
    """Default initializer for all embedders, taking an algorithm and a config.

    An algorithm should be a standalone function that takes a piece of data of an
    InDataType and maps it to an OutDataType. Any extra configuration parameters
    taken as input by the algorithm function should be defined in the config dataclass,
    inheriting from EmbedderConfig.

    Arguments:
        algorithm: a callable to the algorithm function.
        config: a config dataclass holding parameter values for the algorithm.
    """

    algo_signature = inspect.signature(algorithm)

    if not isinstance(config, EmbedderConfig):
        raise TypeError(
            "The config must be an instance of a dataclass inheriting from EmbedderConfig."
        )

    if not set(config.dict().keys()) <= set(algo_signature.parameters):
        raise KeyError(
            f"Config {config.__class__.__name__} is not compatible with the "
            + f"algorithm {algorithm.__name__}, as not all configuration fields "
            + "correspond to keyword arguments in the algorithm function."
        )

    self._algorithm = algorithm
    self._config = config
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: InDataType) -> OutDataType

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data
    (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
    """Validates the input, runs the embedding algorithm, and validates the output.

    Arguments:
        data: the data to embed.
    """
    self.validate_input(data)
    result: OutDataType = self.algorithm(data, **self.config.dict())
    self.validate_output(result)
    return result
validate_input(data: InDataType) -> None abstractmethod

Checks if the given data is compatible with the embedder.

Each embedder should write its own data validator. If the data is not of the supported type or in the specific supported format for that embedder, an error should be raised.

Parameters:

  • data
    (InDataType) –

    the data to validate.

Raises:

  • TypeError

    if the data is not of the supported type.

  • SomeError

    some other error if other constraints are not met.

Source code in qoolqit/embedding/base_embedder.py
@abstractmethod
def validate_input(self, data: InDataType) -> None:
    """Checks if the given data is compatible with the embedder.

    Each embedder should write its own data validator. If the data
    is not of the supported type or in the specific supported format
    for that embedder, an error should be raised.

    Arguments:
        data: the data to validate.

    Raises:
        TypeError: if the data is not of the supported type.
        SomeError: some other error if other constraints are not met.
    """
    ...
validate_output(result: OutDataType) -> None abstractmethod

Checks if the resulting output is expected by the embedder.

Each embedder should write its own output validator. If the result is not of the supported type or in the specific supported format for that embedder, an error should be raised.

Parameters:

  • result
    (OutDataType) –

    the output to validate.

Raises:

  • TypeError

    if the output is not of the supported type.

  • SomeError

    some other error if other constraints are not met.

Source code in qoolqit/embedding/base_embedder.py
@abstractmethod
def validate_output(self, result: OutDataType) -> None:
    """Checks if the resulting output is expected by the embedder.

    Each embedder should write its own output validator. If the result
    is not of the supported type or in the specific supported format
    for that embedder, an error should be raised.

    Arguments:
        result: the output to validate.

    Raises:
        TypeError: if the output is not of the supported type.
        SomeError: some other error if other constraints are not met.
    """
    ...

Blade(config: BladeConfig = BladeConfig())

A matrix to graph embedder using the BLaDE algorithm.

Parameters:

Methods:

  • embed

    Return a DataGraph with coordinates that embeds the input matrix.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/matrix_embedder.py
def __init__(self, config: BladeConfig = BladeConfig()) -> None:
    """Inits Blade.

    Args:
        config (BladeConfig): configuration object for the BLaDE algorithm.
    """
    super().__init__(blade, config=config)
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: np.ndarray) -> DataGraph

Return a DataGraph with coordinates that embeds the input matrix.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data
    (ndarray) –

    the matrix to embed into a DataGraph with coordinates.

Source code in qoolqit/embedding/matrix_embedder.py
def embed(self, data: np.ndarray) -> DataGraph:
    """Return a DataGraph with coordinates that embeds the input matrix.

    Validates the input, runs the embedding algorithm, and validates the output.

    Args:
        data (np.ndarray): the matrix to embed into a DataGraph with coordinates.
    """
    self.validate_input(data)
    positions = self.algorithm(data, **self.config.dict())
    graph = DataGraph.from_coordinates(positions.tolist())
    return graph

BladeConfig(max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = lambda x, max_radial_dist: np.inf, starting_ratio_factor: int = 2, draw_steps: bool | list[int] = False, device: InitVar[Device | None] = None) dataclass

Configuration parameters to embed with BLaDE.

Methods:

  • __post_init__

    Post initialization of the BladeConfig dataclass.

  • dict

    Returns the dataclass as a dictionary.

__post_init__(device: Device | None) -> None

Post initialization of the BladeConfig dataclass.

Set the max_min_dist_ratio argument of the blade_embedding algorithm based on the specification of the selected device.

Parameters:

  • device
    (Device) –

    the QoolQit device to use to set the maximum ratio between the maximum radial distance and the minimum pairwise distance between atoms.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def __post_init__(self, device: Device | None) -> None:
    """Post initialization of the `BladeConfig` dataclass.

    Set the `max_min_dist_ratio` argument of the `blade_embedding` algorithm
    based on the specification of the selected device.

    Args:
        device (Device): the QoolQit device to use to set the maximum ratio between the maximum
            radial distance and the minimum pairwise distance between atoms.
    """
    if device:
        if self.max_min_dist_ratio:
            logger.warning(
                "`max_min_dist_ratio` and `device` attributes should not be set simultaneously."
            )
        min_distance = device._min_distance
        max_radial_distance = device._max_radial_distance
        if max_radial_distance and min_distance:
            self.max_min_dist_ratio = max_radial_distance / min_distance
dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)

EmbedderConfig() dataclass

Base abstract dataclass for all embedding algorithm configurations.

Subclasses define parameters specific to their algorithms. Each config should define fields that directly translate to arguments in the respective embedding function it configures.

Methods:

  • dict

    Returns the dataclass as a dictionary.

dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)

GraphToGraphEmbedder(algorithm: Callable, config: ConfigType)

A family of embedders that map a graph to a graph.

Focused on unit-disk graph embedding, where the goal is to find a set of coordinates for a graph that has no coordinates, such that the final unit-disk edges matches the set of edges in the original graph.

A custom algorithm and configuration can be set at initialization.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

  • algorithm
    (Callable) –

    a callable to the algorithm function.

  • config
    (ConfigType) –

    a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
    """Default initializer for all embedders, taking an algorithm and a config.

    An algorithm should be a standalone function that takes a piece of data of an
    InDataType and maps it to an OutDataType. Any extra configuration parameters
    taken as input by the algorithm function should be defined in the config dataclass,
    inheriting from EmbedderConfig.

    Arguments:
        algorithm: a callable to the algorithm function.
        config: a config dataclass holding parameter values for the algorithm.
    """

    algo_signature = inspect.signature(algorithm)

    if not isinstance(config, EmbedderConfig):
        raise TypeError(
            "The config must be an instance of a dataclass inheriting from EmbedderConfig."
        )

    if not set(config.dict().keys()) <= set(algo_signature.parameters):
        raise KeyError(
            f"Config {config.__class__.__name__} is not compatible with the "
            + f"algorithm {algorithm.__name__}, as not all configuration fields "
            + "correspond to keyword arguments in the algorithm function."
        )

    self._algorithm = algorithm
    self._config = config
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: InDataType) -> OutDataType

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data
    (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
    """Validates the input, runs the embedding algorithm, and validates the output.

    Arguments:
        data: the data to embed.
    """
    self.validate_input(data)
    result: OutDataType = self.algorithm(data, **self.config.dict())
    self.validate_output(result)
    return result

InteractionEmbedder()

A matrix to graph embedder using the interaction embedding algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/matrix_embedder.py
def __init__(self) -> None:
    super().__init__(interaction_embedding, InteractionEmbedderConfig())
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: InDataType) -> OutDataType

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data
    (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
    """Validates the input, runs the embedding algorithm, and validates the output.

    Arguments:
        data: the data to embed.
    """
    self.validate_input(data)
    result: OutDataType = self.algorithm(data, **self.config.dict())
    self.validate_output(result)
    return result

InteractionEmbedderConfig(method: str = 'Nelder-Mead', maxiter: int = 200000, tol: float = 1e-08) dataclass

Configuration parameters for the interaction embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)

MatrixToGraphEmbedder(algorithm: Callable, config: ConfigType)

A family of embedders that map a matrix to a graph.

A custom algorithm and configuration can be set at initialization.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

  • algorithm
    (Callable) –

    a callable to the algorithm function.

  • config
    (ConfigType) –

    a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
    """Default initializer for all embedders, taking an algorithm and a config.

    An algorithm should be a standalone function that takes a piece of data of an
    InDataType and maps it to an OutDataType. Any extra configuration parameters
    taken as input by the algorithm function should be defined in the config dataclass,
    inheriting from EmbedderConfig.

    Arguments:
        algorithm: a callable to the algorithm function.
        config: a config dataclass holding parameter values for the algorithm.
    """

    algo_signature = inspect.signature(algorithm)

    if not isinstance(config, EmbedderConfig):
        raise TypeError(
            "The config must be an instance of a dataclass inheriting from EmbedderConfig."
        )

    if not set(config.dict().keys()) <= set(algo_signature.parameters):
        raise KeyError(
            f"Config {config.__class__.__name__} is not compatible with the "
            + f"algorithm {algorithm.__name__}, as not all configuration fields "
            + "correspond to keyword arguments in the algorithm function."
        )

    self._algorithm = algorithm
    self._config = config
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: InDataType) -> OutDataType

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data
    (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
    """Validates the input, runs the embedding algorithm, and validates the output.

    Arguments:
        data: the data to embed.
    """
    self.validate_input(data)
    result: OutDataType = self.algorithm(data, **self.config.dict())
    self.validate_output(result)
    return result

SpringLayoutConfig(iterations: int = 100, threshold: float = 0.0001, seed: int | None = None) dataclass

Configuration parameters for the spring-layout embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)

SpringLayoutEmbedder(config: SpringLayoutConfig = SpringLayoutConfig())

A graph to graph embedder using the spring layout algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/graph_embedder.py
def __init__(self, config: SpringLayoutConfig = SpringLayoutConfig()) -> None:
    """Inits SpringLayoutEmbedder."""
    super().__init__(spring_layout_embedding, config=config)
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: InDataType) -> OutDataType

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data
    (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
    """Validates the input, runs the embedding algorithm, and validates the output.

    Arguments:
        data: the data to embed.
    """
    self.validate_input(data)
    result: OutDataType = self.algorithm(data, **self.config.dict())
    self.validate_output(result)
    return result

algorithms

Modules:

Classes:

BladeConfig(max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = lambda x, max_radial_dist: np.inf, starting_ratio_factor: int = 2, draw_steps: bool | list[int] = False, device: InitVar[Device | None] = None) dataclass

Configuration parameters to embed with BLaDE.

Methods:

  • __post_init__

    Post initialization of the BladeConfig dataclass.

  • dict

    Returns the dataclass as a dictionary.

__post_init__(device: Device | None) -> None

Post initialization of the BladeConfig dataclass.

Set the max_min_dist_ratio argument of the blade_embedding algorithm based on the specification of the selected device.

Parameters:

  • device (Device) –

    the QoolQit device to use to set the maximum ratio between the maximum radial distance and the minimum pairwise distance between atoms.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def __post_init__(self, device: Device | None) -> None:
    """Post initialization of the `BladeConfig` dataclass.

    Set the `max_min_dist_ratio` argument of the `blade_embedding` algorithm
    based on the specification of the selected device.

    Args:
        device (Device): the QoolQit device to use to set the maximum ratio between the maximum
            radial distance and the minimum pairwise distance between atoms.
    """
    if device:
        if self.max_min_dist_ratio:
            logger.warning(
                "`max_min_dist_ratio` and `device` attributes should not be set simultaneously."
            )
        min_distance = device._min_distance
        max_radial_distance = device._max_radial_distance
        if max_radial_distance and min_distance:
            self.max_min_dist_ratio = max_radial_distance / min_distance
dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)
InteractionEmbedderConfig(method: str = 'Nelder-Mead', maxiter: int = 200000, tol: float = 1e-08) dataclass

Configuration parameters for the interaction embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)
SpringLayoutConfig(iterations: int = 100, threshold: float = 0.0001, seed: int | None = None) dataclass

Configuration parameters for the spring-layout embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)
blade

Modules:

Classes:

  • BladeConfig

    Configuration parameters to embed with BLaDE.

BladeConfig(max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = lambda x, max_radial_dist: np.inf, starting_ratio_factor: int = 2, draw_steps: bool | list[int] = False, device: InitVar[Device | None] = None) dataclass

Configuration parameters to embed with BLaDE.

Methods:

  • __post_init__

    Post initialization of the BladeConfig dataclass.

  • dict

    Returns the dataclass as a dictionary.

__post_init__(device: Device | None) -> None

Post initialization of the BladeConfig dataclass.

Set the max_min_dist_ratio argument of the blade_embedding algorithm based on the specification of the selected device.

Parameters:

  • device (Device) –

    the QoolQit device to use to set the maximum ratio between the maximum radial distance and the minimum pairwise distance between atoms.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def __post_init__(self, device: Device | None) -> None:
    """Post initialization of the `BladeConfig` dataclass.

    Set the `max_min_dist_ratio` argument of the `blade_embedding` algorithm
    based on the specification of the selected device.

    Args:
        device (Device): the QoolQit device to use to set the maximum ratio between the maximum
            radial distance and the minimum pairwise distance between atoms.
    """
    if device:
        if self.max_min_dist_ratio:
            logger.warning(
                "`max_min_dist_ratio` and `device` attributes should not be set simultaneously."
            )
        min_distance = device._min_distance
        max_radial_distance = device._max_radial_distance
        if max_radial_distance and min_distance:
            self.max_min_dist_ratio = max_radial_distance / min_distance
dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)
blade

Classes:

  • BladeConfig

    Configuration parameters to embed with BLaDE.

Functions:

BladeConfig(max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = lambda x, max_radial_dist: np.inf, starting_ratio_factor: int = 2, draw_steps: bool | list[int] = False, device: InitVar[Device | None] = None) dataclass

Configuration parameters to embed with BLaDE.

Methods:

  • __post_init__

    Post initialization of the BladeConfig dataclass.

  • dict

    Returns the dataclass as a dictionary.

__post_init__(device: Device | None) -> None

Post initialization of the BladeConfig dataclass.

Set the max_min_dist_ratio argument of the blade_embedding algorithm based on the specification of the selected device.

Parameters:

  • device (Device) –

    the QoolQit device to use to set the maximum ratio between the maximum radial distance and the minimum pairwise distance between atoms.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def __post_init__(self, device: Device | None) -> None:
    """Post initialization of the `BladeConfig` dataclass.

    Set the `max_min_dist_ratio` argument of the `blade_embedding` algorithm
    based on the specification of the selected device.

    Args:
        device (Device): the QoolQit device to use to set the maximum ratio between the maximum
            radial distance and the minimum pairwise distance between atoms.
    """
    if device:
        if self.max_min_dist_ratio:
            logger.warning(
                "`max_min_dist_ratio` and `device` attributes should not be set simultaneously."
            )
        min_distance = device._min_distance
        max_radial_distance = device._max_radial_distance
        if max_radial_distance and min_distance:
            self.max_min_dist_ratio = max_radial_distance / min_distance
dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)
blade(matrix: np.ndarray, *, max_min_dist_ratio: float | None = None, dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2), starting_positions: np.ndarray | None = None, pca: bool = False, steps_per_round: int = 200, compute_weight_relative_threshold: Callable[[float], float] = lambda _: 0.1, compute_max_distance_to_walk: Callable[[float, float | None], float | tuple[float, float, float]] = default_compute_max_distance_to_walk, compute_regulation_cursor: Callable[[float], float] = lambda _: 0.1, compute_ratio_step_factors: Callable[[float], float] = default_compute_ratio_step_factors, ratio_rerun: int = 2, draw_steps: bool | list[int] = False, draw_weighted_graph: bool = False, draw_differences: bool = False) -> np.ndarray

Embed an interaction matrix or QUBO with the BLaDE algorithm.

BLaDE stands for Balanced Latently Dimensional Embedder. It compute positions for nodes so that their interactions approach the desired values. The interactions assume that the interaction coefficient of the device is set to 1. Its typical target is on interaction matrices or QUBOs, but it can also be used for MIS with limitations if the adjacency matrix is converted into a QUBO. The general principle is based on the Fruchterman-Reingold algorithm.

An objective interaction matrix or QUBO between the nodes. It must

be either symmetrical or triangular.

max_min_dist_ratio: If present, set the maximum ratio between the maximum radial distance and the minimum pairwise distances. dimensions: List of numbers of dimensions to explore one after the other. A list with one value is equivalent to a list containing twice the same value. For a 2D embedding, the last value should be 2. Increasing the number of intermediate dimensions can help to escape from local minima. starting_positions: If provided, initial positions to start from. Otherwise, random positions will be generated. The number of dimensions of the starting positions must be lower than or equal to the first dimension to explore. If it is lower, it is added dimensions filled with random values. pca: Whether to apply Principal Component Analysis to prioritize dimensions to keep when transitioning from a space to a space with fewer dimensions. It is disabled by default because it can raise an error when there are too many dimensions compared to the number of nodes. steps_per_round: Number of elementary steps to perform for each dimension transition, where at each step move vectors are computed and applied on the nodes. compute_weight_relative_threshold: Function that is called at each step. It takes a float number between 0 and 1 that represents the progress on the steps. It must return a float number between 0 and 1 that gives a threshold determining which weights are significant (see update_positions to learn more). compute_max_distance_to_walk: Function that is called at each step. It takes a float number between 0 and 1 that represents the progress on the steps, and takes another argument that is set to None when max_min_dist_ratio is not enabled, otherwise, it is set to the maximum radial distance for the current step. It must return a float number that limits the distances nodes can move at one step (see update_positions to learn more). compute_regulation_cursor: Function that is called at each step. It takes a float number between 0 and 1 that represents the progress on the steps. It must return a float number between 0 (no regulation) and 1 (full regulation) that uniformizes the ability for the forces to achieve their objectives at each step by changing priorities. ratio_rerun: When the distance ratio constraint is not met, it defines the maximum number of times the algorithm applies additional computation steps putting the priority on the constraint. compute_ratio_step_factors: Function that is called at the boundaries of the rounds. It defines the target ratio the enforce during the evolution. It acts as a multiplying factor on the target ratio. draw_steps: If it is a boolean, it defines whether to globally enable drawing and traces for nodes and forces (for all steps). If it is a list of integers, it defines a subset of steps to enable such drawing. Requires installing the seaborn library. draw_weighted_graph: For each step with drawing enabled, defines whether to draw a weighted graph representing interactions. draw_differences: For each step with drawing enabled, defines whether to draw the differences between current and target interactions.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def blade(
    matrix: np.ndarray,
    *,
    max_min_dist_ratio: float | None = None,
    dimensions: tuple[int, ...] = (5, 4, 3, 2, 2, 2),
    starting_positions: np.ndarray | None = None,
    pca: bool = False,
    steps_per_round: int = 200,
    compute_weight_relative_threshold: Callable[[float], float] = (lambda _: 0.1),
    compute_max_distance_to_walk: Callable[
        [float, float | None], float | tuple[float, float, float]
    ] = default_compute_max_distance_to_walk,
    compute_regulation_cursor: Callable[[float], float] = (lambda _: 0.1),
    compute_ratio_step_factors: Callable[[float], float] = default_compute_ratio_step_factors,
    ratio_rerun: int = 2,
    draw_steps: bool | list[int] = False,
    draw_weighted_graph: bool = False,
    draw_differences: bool = False,
) -> np.ndarray:
    """
    Embed an interaction matrix or QUBO with the BLaDE algorithm.

    BLaDE stands for Balanced Latently Dimensional Embedder.
    It compute positions for nodes so that their interactions
    approach the desired values. The interactions assume that the
    interaction coefficient of the device is set to 1.
    Its typical target is on interaction matrices or QUBOs, but it can also be used
    for MIS with limitations if the adjacency matrix is converted into a QUBO.
    The general principle is based on the Fruchterman-Reingold algorithm.

    matrix: An objective interaction matrix or QUBO between the nodes. It must
        be either symmetrical or triangular.
    max_min_dist_ratio: If present, set the maximum ratio between
        the maximum radial distance and the minimum pairwise distances.
    dimensions: List of numbers of dimensions to explore one
        after the other. A list with one value is equivalent to a list containing
        twice the same value. For a 2D embedding, the last value should be 2.
        Increasing the number of intermediate dimensions can help to escape
        from local minima.
    starting_positions: If provided, initial positions to start from. Otherwise,
        random positions will be generated. The number of dimensions of the
        starting positions must be lower than or equal to the first dimension
        to explore. If it is lower, it is added dimensions filled with
        random values.
    pca: Whether to apply Principal Component Analysis to prioritize dimensions
        to keep when transitioning from a space to a space with fewer dimensions.
        It is disabled by default because it can raise an error when there are
        too many dimensions compared to the number of nodes.
    steps_per_round: Number of elementary steps to perform for each dimension
        transition, where at each step move vectors are computed and applied
        on the nodes.
    compute_weight_relative_threshold: Function that is called at each step.
        It takes a float number between 0 and 1 that represents the progress
        on the steps. It must return a float number between 0 and 1 that gives
        a threshold determining which weights are significant (see
        `update_positions` to learn more).
    compute_max_distance_to_walk: Function that is called at each step.
        It takes a float number between 0 and 1 that represents the progress
        on the steps, and takes another argument that is set to `None` when
        `max_min_dist_ratio` is not enabled, otherwise, it is set to
        the maximum radial distance for the current step.
        It must return a float number that limits the distances
        nodes can move at one step  (see `update_positions` to learn more).
    compute_regulation_cursor: Function that is called at each step.
        It takes a float number between 0 and 1 that represents the progress
        on the steps. It must return a float number between 0 (no regulation)
        and 1 (full regulation) that uniformizes the ability for the forces
        to achieve their objectives at each step by changing priorities.
    ratio_rerun: When the distance ratio constraint is not met, it defines
        the maximum number of times the algorithm applies additional
        computation steps putting the priority on the constraint.
    compute_ratio_step_factors: Function that is called at the boundaries of
        the rounds. It defines the target ratio the enforce during the
        evolution. It acts as a multiplying factor on the target ratio.
    draw_steps: If it is a boolean, it defines whether to globally enable
        drawing and traces for nodes and forces (for all steps). If it is a
        list of integers, it defines a subset of steps to enable such drawing.
        Requires installing the seaborn library.
    draw_weighted_graph: For each step with drawing enabled, defines whether
        to draw a weighted graph representing interactions.
    draw_differences: For each step with drawing enabled, defines whether
        to draw the differences between current and target interactions.
    """

    if len(dimensions) == 1:
        dimensions = (dimensions[0], dimensions[0])

    assert len(dimensions) >= 2

    if isinstance(matrix, np.ndarray):
        assert not np.all(matrix == 0)
    else:
        assert not torch.all(matrix == 0)

    graph = Qubo.from_matrix(matrix).as_graph()
    matrix = np.array(
        nx.adjacency_matrix(graph, nodelist=list(range(len(matrix))), weight="weight").toarray()
    )

    if starting_positions is None:
        positions = generate_random_positions(target_interactions=matrix, dimension=dimensions[0])
    elif starting_positions.shape[1] <= dimensions[0]:
        positions = augment_dimensions_with_random_values(
            starting_positions, new_dimensions=dimensions[0] - starting_positions.shape[1]
        )
    else:
        raise ValueError(
            f"The number of dimensions in the starting positions "
            f"{starting_positions.shape[1]} is greater than the starting "
            f"number of dimensions {dimensions[0]}."
        )

    total_steps = steps_per_round * (len(dimensions) - 1)

    def step_to_progress(step: int) -> float:
        return step / (total_steps - 1)

    steps_ratios: list[float | None] = []

    if max_min_dist_ratio is not None:
        steps_ratios = [
            max_min_dist_ratio * compute_ratio_step_factors(progress)
            for progress in np.linspace(0, 1, len(dimensions))
        ]
        starting_min = _compute_min_pairwise_distance(positions)
    else:
        steps_ratios = [None] * len(dimensions)
        starting_min = None

    assert len(dimensions) == len(steps_ratios)

    def compute_weight_relative_threshold_by_step(step: int) -> float:
        return compute_weight_relative_threshold(step_to_progress(step))

    def compute_max_distance_to_walk_by_step(
        step: int, max_radial_dist: float | None
    ) -> float | tuple[float, float, float]:
        return compute_max_distance_to_walk(step_to_progress(step), max_radial_dist)

    def compute_regulation_cursor_by_step(step: int) -> float:
        return compute_regulation_cursor(step_to_progress(step))

    for dim_idx, start_ratio, final_ratio in zip(
        range(len(dimensions) - 1), steps_ratios[:-1], steps_ratios[1:]
    ):
        positions, starting_min = evolve_with_dimension_transition(
            target_interactions=matrix,
            dimensions=dimensions,
            starting_min=starting_min,
            pca=pca,
            start_step=dim_idx * steps_per_round,
            stop_step=(dim_idx + 1) * steps_per_round,
            compute_weight_relative_threshold_by_step=compute_weight_relative_threshold_by_step,
            compute_max_distance_to_walk_by_step=compute_max_distance_to_walk_by_step,
            compute_regulation_cursor_by_step=compute_regulation_cursor_by_step,
            positions=positions,
            final_ratio=final_ratio,
            dim_idx=dim_idx,
            start_ratio=start_ratio,
            draw_steps=draw_steps,
            draw_weighted_graph=draw_weighted_graph,
            draw_differences=draw_differences,
        )

    if max_min_dist_ratio is not None:
        max_radial_dist = max(np.linalg.norm(positions, axis=-1))
        min_atom_dist = _compute_min_pairwise_distance(positions)
        output_ratio = max_radial_dist / min_atom_dist
        if output_ratio > max_min_dist_ratio:

            if ratio_rerun > 0:
                return blade(
                    matrix=matrix,
                    max_min_dist_ratio=max_min_dist_ratio,
                    dimensions=(2, 2, 2),
                    starting_positions=positions,
                    steps_per_round=steps_per_round,
                    compute_max_distance_to_walk=lambda x, max_radial_dist: 0,
                    compute_ratio_step_factors=lambda progress: np.interp(
                        progress, xp=[0, 1 / 2, 1], fp=[0.8, 0.9, 0.98]
                    ),
                    ratio_rerun=ratio_rerun - 1,
                )

            print(
                f"[Warning] Output ratio {output_ratio}"
                f" is higher than required {max_min_dist_ratio}"
            )

    return positions
default_compute_max_distance_to_walk(progress: float, max_radial_dist: float | None) -> float

Default function with rapid then slow decrease to zero of the walking distance.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def default_compute_max_distance_to_walk(progress: float, max_radial_dist: float | None) -> float:
    """Default function with rapid then slow decrease to zero of the walking distance."""
    if max_radial_dist is None:
        return float(np.inf)
    return float(2 * max_radial_dist * (1 - np.sin(np.pi / 2 * progress)))
default_compute_ratio_step_factors(progress: float) -> float

Default function to decrease the ratio slightly too low and then increase.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def default_compute_ratio_step_factors(progress: float) -> float:
    """Default function to decrease the ratio slightly too low and then increase."""
    return float(np.interp(progress, xp=[0, 3 / 5, 1], fp=[2, 0.94, 0.98]))
update_positions(*, positions: np.ndarray, target_interactions: np.ndarray, weight_relative_threshold: float = 0.0, min_dist: float | None = None, max_radius: float | None = None, max_distance_to_walk: float | tuple[float, float, float] = np.inf, regulation_cursor: float = 0.0, draw_step: bool = False, step: int | None = None, draw_weighted_graph: bool = False) -> np.ndarray

Compute vector moves to adjust node positions toward target interactions.

positions: Starting positions of the nodes. target_interactions: Desired interactions. weight_relative_threshold: It is used to compute a weight difference threshold defining which weights differences are significant and should be considered. For this purpose, it is multiplied by the higher weight difference. It is also used to reduce the precision when targeting the objective weights. min_dist: If set, defined the minimum distance that should be met, and creates forces to enforce the constraint. max_radius: If set, defined the maximum radius that should be met, and creates forces to enforce the constraint. max_distance_to_walk: It set, limits the distance that nodes can walk when the forces are applied. It impacts the priorities of the forces because they only consider the slope of the differences in weights that can be targeting with this ceiling. regulation_cursor: A cursor between 0 (no regulation) and 1 (full regulation) to uniformize the ability of the forces to achieve their interaction targets. draw_step: Whether to draw the nodes and the forces. step: Step number.

Source code in qoolqit/embedding/algorithms/blade/blade.py
def update_positions(
    *,
    positions: np.ndarray,
    target_interactions: np.ndarray,
    weight_relative_threshold: float = 0.0,
    min_dist: float | None = None,
    max_radius: float | None = None,
    max_distance_to_walk: float | tuple[float, float, float] = np.inf,
    regulation_cursor: float = 0.0,
    draw_step: bool = False,
    step: int | None = None,
    draw_weighted_graph: bool = False,
) -> np.ndarray:
    """
    Compute vector moves to adjust node positions toward target interactions.

    positions: Starting positions of the nodes.
    target_interactions: Desired interactions.
    weight_relative_threshold: It is used to compute a weight difference
        threshold defining which weights differences are significant and should
        be considered. For this purpose, it is multiplied by the higher weight difference.
        It is also used to reduce the precision when targeting
        the objective weights.
    min_dist: If set, defined the minimum distance that should be met, and
        creates forces to enforce the constraint.
    max_radius: If set, defined the maximum radius that should be met, and
        creates forces to enforce the constraint.
    max_distance_to_walk: It set, limits the distance that nodes can walk
        when the forces are applied. It impacts the priorities
        of the forces because they only consider the slope of the differences
        in weights that can be targeting with this ceiling.
    regulation_cursor: A cursor between 0 (no regulation) and 1 (full
        regulation) to uniformize the ability of the forces to achieve their
        interaction targets.
    draw_step: Whether to draw the nodes and the forces.
    step: Step number.
    """

    if draw_step:
        print(f"{weight_relative_threshold=}")
        print(f"{regulation_cursor=}")

    assert np.array_equal(target_interactions, target_interactions.T)
    n = len(target_interactions)

    positions = np.array(positions, dtype=float)
    nb_positions, space_dimension = positions.shape

    if isinstance(max_distance_to_walk, tuple):
        max_distance_to_walk, min_constr_max_distance_to_walk, max_constr_max_distance_to_walk = (
            max_distance_to_walk
        )
    else:
        min_constr_max_distance_to_walk = np.inf
        max_constr_max_distance_to_walk = np.inf

    assert nb_positions == n

    position_differences = positions[np.newaxis, :] - positions[:, np.newaxis]
    distance_matrix = np.linalg.norm(position_differences, axis=2)
    with np.errstate(divide="ignore", invalid="ignore"):
        unitary_vectors = position_differences / distance_matrix[:, :, np.newaxis]
    unitary_vectors[range(n), range(n)] = np.zeros(space_dimension)
    logger.debug(f"{unitary_vectors=}")

    modulated_target_interactions, interaction_force = compute_interaction_forces(
        distance_matrix=distance_matrix,
        unitary_vectors=unitary_vectors,
        target_weights=target_interactions,
        weight_relative_threshold=weight_relative_threshold,
        max_distance_to_walk=max_distance_to_walk,
    )

    regulated_interaction_force = interaction_force.regulated(regulation_cursor=regulation_cursor)

    if draw_step:
        temp_ratio_before = np.max(np.triu(interaction_force.maximum_temperatures, k=1)) / np.min(
            interaction_force.maximum_temperatures
        )
        print(f"temperature ratio before regulation: {temp_ratio_before}")
        temp_ratio_after = np.max(
            np.triu(regulated_interaction_force.maximum_temperatures, k=1)
        ) / np.min(regulated_interaction_force.maximum_temperatures)
        print(f"temperature ratio after regulation: {temp_ratio_after}")

    min_constr_force = compute_min_dist_constraint_forces(
        min_dist=min_dist,
        distance_matrix=distance_matrix,
        unitary_vectors=unitary_vectors,
    )
    max_constr_force = compute_max_dist_constraint_forces(
        positions=positions,
        max_radius=max_radius,
    )

    interaction_resulting_forces = regulated_interaction_force.get_resulting_forces(
        regulated_interaction_force.get_temperature()
    )

    min_constr_resulting_forces = min_constr_force.get_resulting_forces(
        min_constr_force.get_temperature()
    )
    limited_min_constr_resulting_forces = (
        min_constr_resulting_forces
        * np.minimum(
            1,
            min_constr_max_distance_to_walk / np.linalg.norm(min_constr_resulting_forces, axis=-1),
        )[:, np.newaxis]
    )
    limited_min_constr_resulting_forces[min_constr_resulting_forces == 0] = 0

    max_constr_resulting_forces = max_constr_force.get_resulting_forces(
        max_constr_force.get_temperature()
    )
    limited_max_constr_resulting_forces = (
        max_constr_resulting_forces
        * np.minimum(
            1,
            max_constr_max_distance_to_walk / np.linalg.norm(max_constr_resulting_forces, axis=-1),
        )[:, np.newaxis]
    )
    limited_max_constr_resulting_forces[max_constr_resulting_forces == 0] = 0

    resulting_forces_vectors = (
        interaction_resulting_forces
        + limited_min_constr_resulting_forces
        + limited_max_constr_resulting_forces
    )
    logger.debug(f"{resulting_forces_vectors=}")

    assert not np.any(np.isinf(interaction_resulting_forces)) and not np.any(
        np.isnan(interaction_resulting_forces)
    )
    assert not np.any(np.isinf(min_constr_resulting_forces)) and not np.any(
        np.isnan(min_constr_resulting_forces)
    )
    assert not np.any(np.isinf(max_constr_resulting_forces)) and not np.any(np.isnan(positions))

    if draw_step:
        draw_update_positions_step(
            positions,
            interaction_resulting_forces=interaction_resulting_forces,
            min_constr_resulting_forces=min_constr_resulting_forces,
            max_constr_resulting_forces=max_constr_resulting_forces,
            resulting_forces_vectors=resulting_forces_vectors,
            target_interactions=target_interactions,
            current_interactions=interaction_matrix_from_distances(distance_matrix),
            min_dist=min_dist,
            max_radius=max_radius,
            max_dist_to_walk=max_distance_to_walk,
            step=step,
            modulated_target_interactions=(
                modulated_target_interactions if max_distance_to_walk != np.inf else None
            ),
        )

    for u, force in enumerate(resulting_forces_vectors):
        positions[u] += force

    assert not np.any(np.isnan(positions))

    if draw_step:
        logger.debug(f"Resulting positions = {dict(enumerate(positions))}")
        print(f"Current number of dimensions is {positions.shape[-1]}")
        distances = distance_matrix[np.triu_indices_from(distance_matrix, k=1)]
        print(
            f"{min_dist=}, {max_radius=}, "
            f"current min dist = {np.min(distances)}, "
            f"current max dist = {np.max(distances)}"
        )
        target_interactions_graph = Qubo.from_matrix(target_interactions).as_graph()
        draw_graph_including_actual_weights(
            target_interactions_graph=target_interactions_graph,
            positions=positions,
            draw_weighted_graph=draw_weighted_graph,
        )

    return positions
drawing

Functions:

  • as_2d

    Return the first two coordinates; expects last dim >= 2.

  • draw_set_graph_coords

    Coords are positions in numerical order of the nodes.

as_2d(a: np.ndarray) -> np.ndarray

Return the first two coordinates; expects last dim >= 2.

Source code in qoolqit/embedding/algorithms/blade/drawing.py
def as_2d(a: np.ndarray) -> np.ndarray:
    """Return the first two coordinates; expects last dim >= 2."""
    return a[..., :2]
draw_set_graph_coords(graph: nx.Graph, coords: np.ndarray, edge_labels: dict | None = None) -> None

Coords are positions in numerical order of the nodes.

Source code in qoolqit/embedding/algorithms/blade/drawing.py
def draw_set_graph_coords(
    graph: nx.Graph, coords: np.ndarray, edge_labels: dict | None = None
) -> None:
    """Coords are positions in numerical order of the nodes."""
    nx.set_node_attributes(graph, dict(enumerate(coords)), "pos")
    draw_weighted_graph(graph, edge_labels=edge_labels)
    plt.show()
interaction_embedding

Classes:

Functions:

InteractionEmbedderConfig(method: str = 'Nelder-Mead', maxiter: int = 200000, tol: float = 1e-08) dataclass

Configuration parameters for the interaction embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)
interaction_embedding(matrix: np.ndarray, method: str, maxiter: int, tol: float) -> DataGraph

Matrix embedding into the interaction term of the Rydberg Analog Model.

Uses scipy.minimize to find the optimal set of node coordinates such that the matrix of values 1/(r_ij)^6 approximate the off-diagonal terms of the input matrix.

Check the documentation for scipy.minimize for more information about each parameter: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html

Parameters:

  • matrix (ndarray) –

    the matrix to embed.

  • method (str) –

    the method used by scipy.minimize.

  • maxiter (int) –

    maximum number of iterations.

  • tol (float) –

    tolerance for termination.

Source code in qoolqit/embedding/algorithms/interaction_embedding.py
def interaction_embedding(matrix: np.ndarray, method: str, maxiter: int, tol: float) -> DataGraph:
    """Matrix embedding into the interaction term of the Rydberg Analog Model.

    Uses scipy.minimize to find the optimal set of node coordinates such that the
    matrix of values 1/(r_ij)^6 approximate the off-diagonal terms of the input matrix.

    Check the documentation for scipy.minimize for more information about each parameter:
    https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html

    Arguments:
        matrix: the matrix to embed.
        method: the method used by scipy.minimize.
        maxiter: maximum number of iterations.
        tol: tolerance for termination.
    """

    def cost_function(new_coords: np.ndarray, matrix: np.ndarray) -> np.floating[Any]:
        """Cost function."""
        new_coords = np.reshape(new_coords, (len(matrix), 2))
        # Cost based on minimizing the distance between the matrix and the interaction 1/r^6
        new_matrix = squareform(1.0 / (pdist(new_coords) ** 6))
        return np.linalg.norm(new_matrix - matrix)

    np.random.seed(0)

    # Initial guess for the coordinates
    x0 = np.random.random(len(matrix) * 2)

    res = minimize(
        cost_function,
        x0,
        args=(matrix,),
        method=method,
        tol=tol,
        options={"maxiter": maxiter},
    )

    coords = np.reshape(res.x, (len(matrix), 2))

    centered_coords = coords - np.mean(coords, axis=0)

    graph = DataGraph.from_coordinates(centered_coords.tolist())

    return graph
spring_layout_embedding

Classes:

Functions:

SpringLayoutConfig(iterations: int = 100, threshold: float = 0.0001, seed: int | None = None) dataclass

Configuration parameters for the spring-layout embedding.

Methods:

  • dict

    Returns the dataclass as a dictionary.

dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)
spring_layout_embedding(graph: DataGraph, iterations: int, threshold: float, seed: int | None) -> DataGraph

Force-directed embedding, wrapping nx.spring_layout.

Generates a new graph with the same nodes and edges as the original graph, but with node coordinates set to embed edge weights into pairwise distances as wᵢⱼ=1/rᵢⱼ^6.

The positions are generated by nx.spring_layout. Since nx.spring_layout embeds edge weights as wᵢⱼ = (k/rᵢⱼ)^3, we set: - k=1 and scale=None to disable global rescaling of positions. - rescale weights wᵢⱼ -> wᵢⱼ^1/2 to achieve our target embedding wᵢⱼ=1/rᵢⱼ^6.

Check the documentation for nx.spring_layout for more information about each parameter: https://networkx.org/documentation/stable/reference/generated/networkx.drawing.layout.spring_layout.html

Parameters:

  • graph (DataGraph) –

    the graph to embed according to its edge weights.

  • iterations (int) –

    maximum number of iterations to take.

  • threshold (float) –

    Threshold for relative error in node position changes. The iteration stops if force-displacement is below this threshold.

  • seed (int | None) –

    random seed for reproducibility.

Returns:

  • DataGraph ( DataGraph ) –

    graph with the node coordinates set according to the spring-layout embedding.

Source code in qoolqit/embedding/algorithms/spring_layout_embedding.py
def spring_layout_embedding(
    graph: DataGraph,
    iterations: int,
    threshold: float,
    seed: int | None,
) -> DataGraph:
    """Force-directed embedding, wrapping `nx.spring_layout`.

    Generates a new graph with the same nodes and edges as the original graph, but with
    node coordinates set to embed edge weights into pairwise distances as wᵢⱼ=1/rᵢⱼ^6.

    The positions are generated by `nx.spring_layout`.
    Since `nx.spring_layout` embeds edge weights as wᵢⱼ = (k/rᵢⱼ)^3, we set:
     - `k=1` and `scale=None` to disable global rescaling of positions.
     - rescale weights wᵢⱼ -> wᵢⱼ^1/2
    to achieve our target embedding wᵢⱼ=1/rᵢⱼ^6.

    Check the documentation for `nx.spring_layout` for more information about each parameter:
    https://networkx.org/documentation/stable/reference/generated/networkx.drawing.layout.spring_layout.html

    Args:
        graph: the graph to embed according to its edge weights.
        iterations: maximum number of iterations to take.
        threshold: Threshold for relative error in node position changes.
            The iteration stops if force-displacement is below this threshold.
        seed: random seed for reproducibility.

    Returns:
        DataGraph: graph with the node coordinates set according to the spring-layout embedding.
    """
    # Create a copy of the input graph to avoid modifying weights in-place
    graph_for_layout = graph.copy()
    # Modify edge weights to embed into wᵢⱼ=1/rᵢⱼ^6
    for u, v, weight in graph.edges.data("weight"):
        if weight is not None:
            graph_for_layout.edges[u, v]["weight"] = np.sqrt(weight)

    # Generate the positions using spring_layout with the modified edge weights
    positions = nx.spring_layout(
        graph_for_layout, k=1, scale=None, iterations=iterations, threshold=threshold, seed=seed
    )

    # Create a new graph with the same nodes and edges as the original
    # but with node coordinates set to be the positions generated by spring_layout
    output_graph: DataGraph = graph.copy()
    nx.set_node_attributes(output_graph, values=positions, name="pos")
    output_graph.coords = positions

    return output_graph

base_embedder

Classes:

  • BaseEmbedder

    Abstract base class for all embedders.

  • EmbedderConfig

    Base abstract dataclass for all embedding algorithm configurations.

BaseEmbedder(algorithm: Callable, config: ConfigType)

Abstract base class for all embedders.

An embedder is a function that maps a InDataType to an OutDataType through an embedding algorithm. Parameters of the embedding algorithm can be customized through the EmbedderConfig.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

  • algorithm
    (Callable) –

    a callable to the algorithm function.

  • config
    (ConfigType) –

    a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

  • validate_input

    Checks if the given data is compatible with the embedder.

  • validate_output

    Checks if the resulting output is expected by the embedder.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
    """Default initializer for all embedders, taking an algorithm and a config.

    An algorithm should be a standalone function that takes a piece of data of an
    InDataType and maps it to an OutDataType. Any extra configuration parameters
    taken as input by the algorithm function should be defined in the config dataclass,
    inheriting from EmbedderConfig.

    Arguments:
        algorithm: a callable to the algorithm function.
        config: a config dataclass holding parameter values for the algorithm.
    """

    algo_signature = inspect.signature(algorithm)

    if not isinstance(config, EmbedderConfig):
        raise TypeError(
            "The config must be an instance of a dataclass inheriting from EmbedderConfig."
        )

    if not set(config.dict().keys()) <= set(algo_signature.parameters):
        raise KeyError(
            f"Config {config.__class__.__name__} is not compatible with the "
            + f"algorithm {algorithm.__name__}, as not all configuration fields "
            + "correspond to keyword arguments in the algorithm function."
        )

    self._algorithm = algorithm
    self._config = config
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: InDataType) -> OutDataType

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
    """Validates the input, runs the embedding algorithm, and validates the output.

    Arguments:
        data: the data to embed.
    """
    self.validate_input(data)
    result: OutDataType = self.algorithm(data, **self.config.dict())
    self.validate_output(result)
    return result
validate_input(data: InDataType) -> None abstractmethod

Checks if the given data is compatible with the embedder.

Each embedder should write its own data validator. If the data is not of the supported type or in the specific supported format for that embedder, an error should be raised.

Parameters:

  • data (InDataType) –

    the data to validate.

Raises:

  • TypeError

    if the data is not of the supported type.

  • SomeError

    some other error if other constraints are not met.

Source code in qoolqit/embedding/base_embedder.py
@abstractmethod
def validate_input(self, data: InDataType) -> None:
    """Checks if the given data is compatible with the embedder.

    Each embedder should write its own data validator. If the data
    is not of the supported type or in the specific supported format
    for that embedder, an error should be raised.

    Arguments:
        data: the data to validate.

    Raises:
        TypeError: if the data is not of the supported type.
        SomeError: some other error if other constraints are not met.
    """
    ...
validate_output(result: OutDataType) -> None abstractmethod

Checks if the resulting output is expected by the embedder.

Each embedder should write its own output validator. If the result is not of the supported type or in the specific supported format for that embedder, an error should be raised.

Parameters:

  • result (OutDataType) –

    the output to validate.

Raises:

  • TypeError

    if the output is not of the supported type.

  • SomeError

    some other error if other constraints are not met.

Source code in qoolqit/embedding/base_embedder.py
@abstractmethod
def validate_output(self, result: OutDataType) -> None:
    """Checks if the resulting output is expected by the embedder.

    Each embedder should write its own output validator. If the result
    is not of the supported type or in the specific supported format
    for that embedder, an error should be raised.

    Arguments:
        result: the output to validate.

    Raises:
        TypeError: if the output is not of the supported type.
        SomeError: some other error if other constraints are not met.
    """
    ...
EmbedderConfig() dataclass

Base abstract dataclass for all embedding algorithm configurations.

Subclasses define parameters specific to their algorithms. Each config should define fields that directly translate to arguments in the respective embedding function it configures.

Methods:

  • dict

    Returns the dataclass as a dictionary.

dict() -> dict

Returns the dataclass as a dictionary.

Source code in qoolqit/embedding/base_embedder.py
def dict(self) -> dict:
    """Returns the dataclass as a dictionary."""
    return asdict(self)

graph_embedder

Classes:

GraphToGraphEmbedder(algorithm: Callable, config: ConfigType)

A family of embedders that map a graph to a graph.

Focused on unit-disk graph embedding, where the goal is to find a set of coordinates for a graph that has no coordinates, such that the final unit-disk edges matches the set of edges in the original graph.

A custom algorithm and configuration can be set at initialization.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

  • algorithm
    (Callable) –

    a callable to the algorithm function.

  • config
    (ConfigType) –

    a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
    """Default initializer for all embedders, taking an algorithm and a config.

    An algorithm should be a standalone function that takes a piece of data of an
    InDataType and maps it to an OutDataType. Any extra configuration parameters
    taken as input by the algorithm function should be defined in the config dataclass,
    inheriting from EmbedderConfig.

    Arguments:
        algorithm: a callable to the algorithm function.
        config: a config dataclass holding parameter values for the algorithm.
    """

    algo_signature = inspect.signature(algorithm)

    if not isinstance(config, EmbedderConfig):
        raise TypeError(
            "The config must be an instance of a dataclass inheriting from EmbedderConfig."
        )

    if not set(config.dict().keys()) <= set(algo_signature.parameters):
        raise KeyError(
            f"Config {config.__class__.__name__} is not compatible with the "
            + f"algorithm {algorithm.__name__}, as not all configuration fields "
            + "correspond to keyword arguments in the algorithm function."
        )

    self._algorithm = algorithm
    self._config = config
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: InDataType) -> OutDataType

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
    """Validates the input, runs the embedding algorithm, and validates the output.

    Arguments:
        data: the data to embed.
    """
    self.validate_input(data)
    result: OutDataType = self.algorithm(data, **self.config.dict())
    self.validate_output(result)
    return result
SpringLayoutEmbedder(config: SpringLayoutConfig = SpringLayoutConfig())

A graph to graph embedder using the spring layout algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/graph_embedder.py
def __init__(self, config: SpringLayoutConfig = SpringLayoutConfig()) -> None:
    """Inits SpringLayoutEmbedder."""
    super().__init__(spring_layout_embedding, config=config)
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: InDataType) -> OutDataType

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
    """Validates the input, runs the embedding algorithm, and validates the output.

    Arguments:
        data: the data to embed.
    """
    self.validate_input(data)
    result: OutDataType = self.algorithm(data, **self.config.dict())
    self.validate_output(result)
    return result

matrix_embedder

Classes:

  • Blade

    A matrix to graph embedder using the BLaDE algorithm.

  • InteractionEmbedder

    A matrix to graph embedder using the interaction embedding algorithm.

  • MatrixToGraphEmbedder

    A family of embedders that map a matrix to a graph.

Blade(config: BladeConfig = BladeConfig())

A matrix to graph embedder using the BLaDE algorithm.

Parameters:

Methods:

  • embed

    Return a DataGraph with coordinates that embeds the input matrix.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/matrix_embedder.py
def __init__(self, config: BladeConfig = BladeConfig()) -> None:
    """Inits Blade.

    Args:
        config (BladeConfig): configuration object for the BLaDE algorithm.
    """
    super().__init__(blade, config=config)
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: np.ndarray) -> DataGraph

Return a DataGraph with coordinates that embeds the input matrix.

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (ndarray) –

    the matrix to embed into a DataGraph with coordinates.

Source code in qoolqit/embedding/matrix_embedder.py
def embed(self, data: np.ndarray) -> DataGraph:
    """Return a DataGraph with coordinates that embeds the input matrix.

    Validates the input, runs the embedding algorithm, and validates the output.

    Args:
        data (np.ndarray): the matrix to embed into a DataGraph with coordinates.
    """
    self.validate_input(data)
    positions = self.algorithm(data, **self.config.dict())
    graph = DataGraph.from_coordinates(positions.tolist())
    return graph
InteractionEmbedder()

A matrix to graph embedder using the interaction embedding algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/matrix_embedder.py
def __init__(self) -> None:
    super().__init__(interaction_embedding, InteractionEmbedderConfig())
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: InDataType) -> OutDataType

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
    """Validates the input, runs the embedding algorithm, and validates the output.

    Arguments:
        data: the data to embed.
    """
    self.validate_input(data)
    result: OutDataType = self.algorithm(data, **self.config.dict())
    self.validate_output(result)
    return result
MatrixToGraphEmbedder(algorithm: Callable, config: ConfigType)

A family of embedders that map a matrix to a graph.

A custom algorithm and configuration can be set at initialization.

An algorithm should be a standalone function that takes a piece of data of an InDataType and maps it to an OutDataType. Any extra configuration parameters taken as input by the algorithm function should be defined in the config dataclass, inheriting from EmbedderConfig.

Parameters:

  • algorithm
    (Callable) –

    a callable to the algorithm function.

  • config
    (ConfigType) –

    a config dataclass holding parameter values for the algorithm.

Methods:

  • embed

    Validates the input, runs the embedding algorithm, and validates the output.

Attributes:

  • algorithm (Callable) –

    Returns the callable to the embedding algorithm.

  • config (ConfigType) –

    Returns the config for the embedding algorithm.

  • info (str) –

    Prints info about the embedding algorithm.

Source code in qoolqit/embedding/base_embedder.py
def __init__(self, algorithm: Callable, config: ConfigType) -> None:
    """Default initializer for all embedders, taking an algorithm and a config.

    An algorithm should be a standalone function that takes a piece of data of an
    InDataType and maps it to an OutDataType. Any extra configuration parameters
    taken as input by the algorithm function should be defined in the config dataclass,
    inheriting from EmbedderConfig.

    Arguments:
        algorithm: a callable to the algorithm function.
        config: a config dataclass holding parameter values for the algorithm.
    """

    algo_signature = inspect.signature(algorithm)

    if not isinstance(config, EmbedderConfig):
        raise TypeError(
            "The config must be an instance of a dataclass inheriting from EmbedderConfig."
        )

    if not set(config.dict().keys()) <= set(algo_signature.parameters):
        raise KeyError(
            f"Config {config.__class__.__name__} is not compatible with the "
            + f"algorithm {algorithm.__name__}, as not all configuration fields "
            + "correspond to keyword arguments in the algorithm function."
        )

    self._algorithm = algorithm
    self._config = config
algorithm: Callable property

Returns the callable to the embedding algorithm.

config: ConfigType property

Returns the config for the embedding algorithm.

info: str property

Prints info about the embedding algorithm.

embed(data: InDataType) -> OutDataType

Validates the input, runs the embedding algorithm, and validates the output.

Parameters:

  • data (InDataType) –

    the data to embed.

Source code in qoolqit/embedding/base_embedder.py
def embed(self, data: InDataType) -> OutDataType:
    """Validates the input, runs the embedding algorithm, and validates the output.

    Arguments:
        data: the data to embed.
    """
    self.validate_input(data)
    result: OutDataType = self.algorithm(data, **self.config.dict())
    self.validate_output(result)
    return result

exceptions

Classes:

  • CompilationError

    An error raised when attempting to compile a program into a Pulser Sequence.

CompilationError

An error raised when attempting to compile a program into a Pulser Sequence.

execution

QoolQit module to execute quantum programs on QPUs or local/remote emulators.

Modules:

Classes:

  • LocalEmulator

    Run QoolQit QuantumPrograms on a Pasqal local emulator backends.

  • QPU

    Execute QoolQit QuantumPrograms on Pasqal quantum processing units.

  • RemoteEmulator

    Run QoolQit QuantumPrograms on a Pasqal remote emulator backends.

  • SequenceCompiler

    Compiles a QoolQit Register and Drive to a Device.

LocalEmulator(*, backend_type: type[EmulatorBackend] = QutipBackendV2, emulation_config: EmulationConfig | None = None, num_shots: int | None = None)

Run QoolQit QuantumPrograms on a Pasqal local emulator backends.

This class serves as a primary interface between tools written using QoolQit (including solvers) and local emulator backends.

Parameters:

  • backend_type
    (type, default: QutipBackendV2 ) –

    backend type. Must be a subtype of pulser.backend.EmulatorBackend.

  • emulation_config
    (EmulationConfig, default: None ) –

    optional configuration object emulators.

  • num_shots
    (int, default: None ) –

    number of bitstring samples to collect from the final quantum state.

Examples:

from qoolqit.execution import LocalEmulator, BackendType
backend = LocalEmulator(backend_type=BackendType.QutipBackendV2)
result = backend.run(program)

Methods:

Source code in qoolqit/execution/backends.py
def __init__(
    self,
    *,
    backend_type: type[EmulatorBackend] = QutipBackendV2,
    emulation_config: EmulationConfig | None = None,
    num_shots: int | None = None,
) -> None:
    """Instantiates a LocalEmulator."""
    super().__init__(num_shots=num_shots)
    if not issubclass(backend_type, EmulatorBackend):
        raise TypeError(
            "Error in `LocalEmulator`: `backend_type` must be a EmulatorBackend type."
        )
    self._backend_type = backend_type
    self._emulation_config = self.validate_emulation_config(emulation_config)
default_emulation_config() -> EmulationConfig

Return a unique emulation config for all emulators.

Defaults to a configuration that asks for the final bitstring, sampled num_shots times.

Source code in qoolqit/execution/backends.py
def default_emulation_config(self) -> EmulationConfig:
    """Return a unique emulation config for all emulators.

    Defaults to a configuration that asks for the final bitstring, sampled `num_shots` times.
    """
    return EmulationConfig(observables=(BitStrings(num_shots=self._num_shots),))
run(program: QuantumProgram) -> Sequence[Results]

Run a compiled QuantumProgram and return the results.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
    """Run a compiled QuantumProgram and return the results."""
    self._backend = self._backend_type(program.compiled_sequence, config=self._emulation_config)
    results = self._backend.run()
    res_seq = (results,) if isinstance(results, Results) else tuple(results)
    return res_seq
validate_emulation_config(emulation_config: EmulationConfig | None) -> EmulationConfig

Returns a valid config for emulator backends, if needed.

Parameters:

  • emulation_config
    (EmulationConfig | None) –

    optional base configuration class for all emulators backends. If no config is provided to an emulator backend, the backend default will used.

Source code in qoolqit/execution/backends.py
def validate_emulation_config(
    self, emulation_config: EmulationConfig | None
) -> EmulationConfig:
    """Returns a valid config for emulator backends, if needed.

    Args:
        emulation_config: optional base configuration class for all emulators backends.
            If no config is provided to an emulator backend, the backend default will used.
    """
    if emulation_config is None:
        emulation_config = self.default_emulation_config()
    else:
        has_bitstrings = any(
            isinstance(obs, BitStrings) for obs in emulation_config.observables
        )
        if not has_bitstrings:
            updated_obs = (*emulation_config.observables, BitStrings(num_shots=self._num_shots))
            emulation_config = emulation_config.with_changes(observables=updated_obs)

    if self._num_shots is not None:
        emulation_config = emulation_config.with_changes(default_num_shots=self._num_shots)

    return emulation_config

QPU(*, connection: RemoteConnection, num_shots: int | None = None)

Execute QoolQit QuantumPrograms on Pasqal quantum processing units.

This class provides the primary interface for running quantum programs on actual QPU hardware. It requires authenticated credentials through a connection object to submit and execute programs on remote quantum processors.

Parameters:

  • connection
    (RemoteConnection) –

    Authenticated connection to the remote QPU backend.

  • num_shots
    (int | None, default: None ) –

    Number of bitstring samples to collect from the final quantum state.

Examples:

Using Pasqal Cloud:

from pulser_pasqal import PasqalCloud
from qoolqit.execution import QPU

connection = PasqalCloud(
    username="your_username",
    password="your_password",
    project_id="your_project_id"
)
backend = QPU(connection=connection)
remote_results = backend.submit(program)

Using Atos MyQML:

from pulser_myqlm import PulserQLMConnection
from qoolqit.execution import QPU

connection = PulserQLMConnection()
backend = QPU(connection=connection)
results = backend.run(program)

Note

Contact your quantum computing provider for credentials and connection setup: - Pasqal Cloud Documentation - Atos MyQML Framework

Methods:

  • run

    Execute a compiled quantum program on the QPU and return the results.

  • submit

    Submit a compiled quantum program to the QPU and return a result handler.

  • validate_connection

    Validate the required connection to instantiate a RemoteBackend.

Source code in qoolqit/execution/backends.py
def __init__(
    self,
    *,
    connection: RemoteConnection,
    num_shots: int | None = None,
) -> None:
    """Instantiates a QPU backend."""
    self._backend_type = QPUBackend
    self._connection = self.validate_connection(connection)
    if num_shots is None:
        raise ValueError(
            """Number of shots must be provided to use the QPU backend.
            Please specify `num_shots` when creating the QPU instance.
            For example: QPU(connection=..., num_shots=100)""",
        )
    self._config = BackendConfig(default_num_shots=num_shots)
run(program: QuantumProgram) -> Sequence[Results]

Execute a compiled quantum program on the QPU and return the results.

This method submits the program and waits for completion before returning the final results.

Parameters:

  • program
    (QuantumProgram) –

    The compiled quantum program to execute on the QPU.

Returns:

  • Sequence[Results]

    The execution results from the QPU.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
    """Execute a compiled quantum program on the QPU and return the results.

    This method submits the program and waits for completion before returning
    the final results.

    Args:
        program: The compiled quantum program to execute on the QPU.

    Returns:
        The execution results from the QPU.
    """
    remote_results = self.submit(program, wait=True)
    res_seq: Sequence[Results] = remote_results.results
    return res_seq
submit(program: QuantumProgram, wait: bool = False) -> RemoteResults

Submit a compiled quantum program to the QPU and return a result handler.

Parameters:

  • program
    (QuantumProgram) –

    The compiled quantum program to execute on the QPU.

  • wait
    (bool, default: False ) –

    Whether to wait for the QPU to complete execution before returning.

Returns:

  • RemoteResults

    A remote result handler for monitoring job status and retrieving results.

Note

The returned RemoteResults object provides: - Job status monitoring via get_batch_status() - Result retrieval via the results property (when job is complete)

Source code in qoolqit/execution/backends.py
def submit(self, program: QuantumProgram, wait: bool = False) -> RemoteResults:
    """Submit a compiled quantum program to the QPU and return a result handler.

    Args:
        program: The compiled quantum program to execute on the QPU.
        wait: Whether to wait for the QPU to complete execution before returning.

    Returns:
        A remote result handler for monitoring job status and retrieving results.

    Note:
        The returned RemoteResults object provides:
        - Job status monitoring via `get_batch_status()`
        - Result retrieval via the `results` property (when job is complete)
    """
    self._backend = self._backend_type(
        program.compiled_sequence, connection=self._connection, config=self._config
    )
    remote_results = self._backend.run(wait=wait)
    return remote_results
validate_connection(connection: RemoteConnection) -> RemoteConnection staticmethod

Validate the required connection to instantiate a RemoteBackend.

Remote emulators and QPUs require a pulser.backend.remote.RemoteConnection or derived to send jobs. Validation also happens inside the backend. Early validation just makes the error easier to understand.

Source code in qoolqit/execution/backends.py
@staticmethod
def validate_connection(connection: RemoteConnection) -> RemoteConnection:
    """Validate the required connection to instantiate a RemoteBackend.

    Remote emulators and QPUs require a `pulser.backend.remote.RemoteConnection` or derived
    to send jobs. Validation also happens inside the backend. Early validation just makes the
    error easier to understand.
    """
    if not isinstance(connection, RemoteConnection):
        raise TypeError(f"""Error in `PulserRemoteBackend`:
            `connection` must be of type {RemoteConnection}.""")
    return connection

RemoteEmulator(*, backend_type: type[RemoteEmulatorBackend] = EmuFreeBackendV2, connection: RemoteConnection, emulation_config: EmulationConfig | None = None, num_shots: int | None = None)

Run QoolQit QuantumPrograms on a Pasqal remote emulator backends.

This class serves as a primary interface between tools written using QoolQit (including solvers) and remote emulator backends. The behavior is similar to LocalEmulator, but here, requires credentials through a connection to submit/run a program. To get your credentials and to create a connection object, please refer to the Pasqal Cloud interface documentation.

Parameters:

  • backend_type
    (type, default: EmuFreeBackendV2 ) –

    backend type. Must be a subtype of pulser_pasqal.backends.RemoteEmulatorBackend.

  • connection
    (RemoteConnection) –

    connection to execute the program on remote backends.

  • emulation_config
    (EmulationConfig, default: None ) –

    optional configuration object emulators.

  • num_shots
    (int, default: None ) –

    number of bitstring samples to collect from the final quantum state.

Examples:

from pulser_pasqal import PasqalCloud
from qoolqit.execution import RemoteEmulator, BackendType
connection = PasqalCloud(username=..., password=..., project_id=...)
backend = RemoteEmulator(backend_type=BackendType.EmuFreeBackendV2, connection=connection)
then
remote_results = backend.submit(program)
or
results = backend.run(program)

Methods:

  • default_emulation_config

    Return a unique emulation config for all emulators.

  • run

    Run a compiled QuantumProgram remotely and return the results.

  • submit

    Submit a compiled QuantumProgram and return a remote handler of the results.

  • validate_connection

    Validate the required connection to instantiate a RemoteBackend.

  • validate_emulation_config

    Returns a valid config for emulator backends, if needed.

Source code in qoolqit/execution/backends.py
def __init__(
    self,
    *,
    backend_type: type[RemoteEmulatorBackend] = EmuFreeBackendV2,
    connection: RemoteConnection,
    emulation_config: EmulationConfig | None = None,
    num_shots: int | None = None,
) -> None:
    """Instantiates a RemoteEmulator."""
    super().__init__(num_shots=num_shots)
    if not issubclass(backend_type, RemoteEmulatorBackend):
        raise TypeError(
            "Error in `RemoteEmulator`: `backend_type` must be a RemoteEmulatorBackend type."
        )
    self._backend_type = backend_type
    self._connection = self.validate_connection(connection)
    self._emulation_config = self.validate_emulation_config(emulation_config)
default_emulation_config() -> EmulationConfig

Return a unique emulation config for all emulators.

Defaults to a configuration that asks for the final bitstring, sampled num_shots times.

Source code in qoolqit/execution/backends.py
def default_emulation_config(self) -> EmulationConfig:
    """Return a unique emulation config for all emulators.

    Defaults to a configuration that asks for the final bitstring, sampled `num_shots` times.
    """
    return EmulationConfig(observables=(BitStrings(num_shots=self._num_shots),))
run(program: QuantumProgram) -> Sequence[Results]

Run a compiled QuantumProgram remotely and return the results.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
    """Run a compiled QuantumProgram remotely and return the results."""
    remote_results = self.submit(program, wait=True)
    res_seq: Sequence[Results] = remote_results.results
    return res_seq
submit(program: QuantumProgram, wait: bool = False) -> RemoteResults

Submit a compiled QuantumProgram and return a remote handler of the results.

The returned handler RemoteResults can be used to: - query the job status with remote_results.get_batch_status() - when DONE, retrieve results with remote_results.results

Parameters:

  • program
    (QuantumProgram) –

    the compiled quantum program to run.

  • wait
    (bool, default: False ) –

    Wait for remote backend to complete the job.

Source code in qoolqit/execution/backends.py
def submit(self, program: QuantumProgram, wait: bool = False) -> RemoteResults:
    """Submit a compiled QuantumProgram and return a remote handler of the results.

    The returned handler `RemoteResults` can be used to:
    - query the job status with `remote_results.get_batch_status()`
    - when DONE, retrieve results with `remote_results.results`

    Args:
        program (QuantumProgram): the compiled quantum program to run.
        wait (bool): Wait for remote backend to complete the job.
    """
    # Instantiate backend
    self._backend = self._backend_type(
        program.compiled_sequence,
        connection=self._connection,
        config=self._emulation_config,
    )
    remote_results = self._backend.run(wait=wait)
    return remote_results
validate_connection(connection: RemoteConnection) -> RemoteConnection staticmethod

Validate the required connection to instantiate a RemoteBackend.

Remote emulators and QPUs require a pulser.backend.remote.RemoteConnection or derived to send jobs. Validation also happens inside the backend. Early validation just makes the error easier to understand.

Source code in qoolqit/execution/backends.py
@staticmethod
def validate_connection(connection: RemoteConnection) -> RemoteConnection:
    """Validate the required connection to instantiate a RemoteBackend.

    Remote emulators and QPUs require a `pulser.backend.remote.RemoteConnection` or derived
    to send jobs. Validation also happens inside the backend. Early validation just makes the
    error easier to understand.
    """
    if not isinstance(connection, RemoteConnection):
        raise TypeError(f"""Error in `PulserRemoteBackend`:
            `connection` must be of type {RemoteConnection}.""")
    return connection
validate_emulation_config(emulation_config: EmulationConfig | None) -> EmulationConfig

Returns a valid config for emulator backends, if needed.

Parameters:

  • emulation_config
    (EmulationConfig | None) –

    optional base configuration class for all emulators backends. If no config is provided to an emulator backend, the backend default will used.

Source code in qoolqit/execution/backends.py
def validate_emulation_config(
    self, emulation_config: EmulationConfig | None
) -> EmulationConfig:
    """Returns a valid config for emulator backends, if needed.

    Args:
        emulation_config: optional base configuration class for all emulators backends.
            If no config is provided to an emulator backend, the backend default will used.
    """
    if emulation_config is None:
        emulation_config = self.default_emulation_config()
    else:
        has_bitstrings = any(
            isinstance(obs, BitStrings) for obs in emulation_config.observables
        )
        if not has_bitstrings:
            updated_obs = (*emulation_config.observables, BitStrings(num_shots=self._num_shots))
            emulation_config = emulation_config.with_changes(observables=updated_obs)

    if self._num_shots is not None:
        emulation_config = emulation_config.with_changes(default_num_shots=self._num_shots)

    return emulation_config

SequenceCompiler(register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None)

Compiles a QoolQit Register and Drive to a Device.

Parameters:

  • register
    (Register) –

    the QoolQit Register.

  • drive
    (Drive) –

    the QoolQit Drive.

  • device
    (Device) –

    the QoolQit Device.

  • profile
    (CompilerProfile) –

    the CompilerProfile to use.

  • device_max_duration_ratio
    (float | None, default: None ) –

    optionally set the program duration to a fraction of the device's maximum allowed duration.

Source code in qoolqit/execution/sequence_compiler.py
def __init__(
    self,
    register: Register,
    drive: Drive,
    device: Device,
    profile: CompilerProfile,
    device_max_duration_ratio: float | None = None,
) -> None:
    """Initializes the compiler.

    Args:
        register: the QoolQit Register.
        drive: the QoolQit Drive.
        device: the QoolQit Device.
        profile: the CompilerProfile to use.
        device_max_duration_ratio: optionally set the program duration to a fraction
            of the device's maximum allowed duration.
    """

    self._register = register
    self._drive = drive
    self._device = device
    self._target_device = device._device
    self._profile = profile
    self._device_max_duration_ratio = device_max_duration_ratio
    self._compilation_function: Callable = basic_compilation

backends

Classes:

LocalEmulator(*, backend_type: type[EmulatorBackend] = QutipBackendV2, emulation_config: EmulationConfig | None = None, num_shots: int | None = None)

Run QoolQit QuantumPrograms on a Pasqal local emulator backends.

This class serves as a primary interface between tools written using QoolQit (including solvers) and local emulator backends.

Parameters:

  • backend_type
    (type, default: QutipBackendV2 ) –

    backend type. Must be a subtype of pulser.backend.EmulatorBackend.

  • emulation_config
    (EmulationConfig, default: None ) –

    optional configuration object emulators.

  • num_shots
    (int, default: None ) –

    number of bitstring samples to collect from the final quantum state.

Examples:

from qoolqit.execution import LocalEmulator, BackendType
backend = LocalEmulator(backend_type=BackendType.QutipBackendV2)
result = backend.run(program)

Methods:

Source code in qoolqit/execution/backends.py
def __init__(
    self,
    *,
    backend_type: type[EmulatorBackend] = QutipBackendV2,
    emulation_config: EmulationConfig | None = None,
    num_shots: int | None = None,
) -> None:
    """Instantiates a LocalEmulator."""
    super().__init__(num_shots=num_shots)
    if not issubclass(backend_type, EmulatorBackend):
        raise TypeError(
            "Error in `LocalEmulator`: `backend_type` must be a EmulatorBackend type."
        )
    self._backend_type = backend_type
    self._emulation_config = self.validate_emulation_config(emulation_config)
default_emulation_config() -> EmulationConfig

Return a unique emulation config for all emulators.

Defaults to a configuration that asks for the final bitstring, sampled num_shots times.

Source code in qoolqit/execution/backends.py
def default_emulation_config(self) -> EmulationConfig:
    """Return a unique emulation config for all emulators.

    Defaults to a configuration that asks for the final bitstring, sampled `num_shots` times.
    """
    return EmulationConfig(observables=(BitStrings(num_shots=self._num_shots),))
run(program: QuantumProgram) -> Sequence[Results]

Run a compiled QuantumProgram and return the results.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
    """Run a compiled QuantumProgram and return the results."""
    self._backend = self._backend_type(program.compiled_sequence, config=self._emulation_config)
    results = self._backend.run()
    res_seq = (results,) if isinstance(results, Results) else tuple(results)
    return res_seq
validate_emulation_config(emulation_config: EmulationConfig | None) -> EmulationConfig

Returns a valid config for emulator backends, if needed.

Parameters:

  • emulation_config (EmulationConfig | None) –

    optional base configuration class for all emulators backends. If no config is provided to an emulator backend, the backend default will used.

Source code in qoolqit/execution/backends.py
def validate_emulation_config(
    self, emulation_config: EmulationConfig | None
) -> EmulationConfig:
    """Returns a valid config for emulator backends, if needed.

    Args:
        emulation_config: optional base configuration class for all emulators backends.
            If no config is provided to an emulator backend, the backend default will used.
    """
    if emulation_config is None:
        emulation_config = self.default_emulation_config()
    else:
        has_bitstrings = any(
            isinstance(obs, BitStrings) for obs in emulation_config.observables
        )
        if not has_bitstrings:
            updated_obs = (*emulation_config.observables, BitStrings(num_shots=self._num_shots))
            emulation_config = emulation_config.with_changes(observables=updated_obs)

    if self._num_shots is not None:
        emulation_config = emulation_config.with_changes(default_num_shots=self._num_shots)

    return emulation_config
PulserEmulatorBackend(num_shots: int | None = None)

Base Emulator class.

Parameters:

  • num_shots
    (int | None, default: None ) –

    run the program num_shots times to collect bitstrings statistics. On QPU this represents the actual number of runs of the program. On emulators, the bitstring are sampled from the quantum state num_shots times.

Methods:

Source code in qoolqit/execution/backends.py
def __init__(self, num_shots: int | None = None) -> None:
    self._num_shots = num_shots
default_emulation_config() -> EmulationConfig

Return a unique emulation config for all emulators.

Defaults to a configuration that asks for the final bitstring, sampled num_shots times.

Source code in qoolqit/execution/backends.py
def default_emulation_config(self) -> EmulationConfig:
    """Return a unique emulation config for all emulators.

    Defaults to a configuration that asks for the final bitstring, sampled `num_shots` times.
    """
    return EmulationConfig(observables=(BitStrings(num_shots=self._num_shots),))
validate_emulation_config(emulation_config: EmulationConfig | None) -> EmulationConfig

Returns a valid config for emulator backends, if needed.

Parameters:

  • emulation_config (EmulationConfig | None) –

    optional base configuration class for all emulators backends. If no config is provided to an emulator backend, the backend default will used.

Source code in qoolqit/execution/backends.py
def validate_emulation_config(
    self, emulation_config: EmulationConfig | None
) -> EmulationConfig:
    """Returns a valid config for emulator backends, if needed.

    Args:
        emulation_config: optional base configuration class for all emulators backends.
            If no config is provided to an emulator backend, the backend default will used.
    """
    if emulation_config is None:
        emulation_config = self.default_emulation_config()
    else:
        has_bitstrings = any(
            isinstance(obs, BitStrings) for obs in emulation_config.observables
        )
        if not has_bitstrings:
            updated_obs = (*emulation_config.observables, BitStrings(num_shots=self._num_shots))
            emulation_config = emulation_config.with_changes(observables=updated_obs)

    if self._num_shots is not None:
        emulation_config = emulation_config.with_changes(default_num_shots=self._num_shots)

    return emulation_config
PulserRemoteBackend

Methods:

validate_connection(connection: RemoteConnection) -> RemoteConnection staticmethod

Validate the required connection to instantiate a RemoteBackend.

Remote emulators and QPUs require a pulser.backend.remote.RemoteConnection or derived to send jobs. Validation also happens inside the backend. Early validation just makes the error easier to understand.

Source code in qoolqit/execution/backends.py
@staticmethod
def validate_connection(connection: RemoteConnection) -> RemoteConnection:
    """Validate the required connection to instantiate a RemoteBackend.

    Remote emulators and QPUs require a `pulser.backend.remote.RemoteConnection` or derived
    to send jobs. Validation also happens inside the backend. Early validation just makes the
    error easier to understand.
    """
    if not isinstance(connection, RemoteConnection):
        raise TypeError(f"""Error in `PulserRemoteBackend`:
            `connection` must be of type {RemoteConnection}.""")
    return connection
QPU(*, connection: RemoteConnection, num_shots: int | None = None)

Execute QoolQit QuantumPrograms on Pasqal quantum processing units.

This class provides the primary interface for running quantum programs on actual QPU hardware. It requires authenticated credentials through a connection object to submit and execute programs on remote quantum processors.

Parameters:

  • connection
    (RemoteConnection) –

    Authenticated connection to the remote QPU backend.

  • num_shots
    (int | None, default: None ) –

    Number of bitstring samples to collect from the final quantum state.

Examples:

Using Pasqal Cloud:

from pulser_pasqal import PasqalCloud
from qoolqit.execution import QPU

connection = PasqalCloud(
    username="your_username",
    password="your_password",
    project_id="your_project_id"
)
backend = QPU(connection=connection)
remote_results = backend.submit(program)

Using Atos MyQML:

from pulser_myqlm import PulserQLMConnection
from qoolqit.execution import QPU

connection = PulserQLMConnection()
backend = QPU(connection=connection)
results = backend.run(program)

Note

Contact your quantum computing provider for credentials and connection setup: - Pasqal Cloud Documentation - Atos MyQML Framework

Methods:

  • run

    Execute a compiled quantum program on the QPU and return the results.

  • submit

    Submit a compiled quantum program to the QPU and return a result handler.

  • validate_connection

    Validate the required connection to instantiate a RemoteBackend.

Source code in qoolqit/execution/backends.py
def __init__(
    self,
    *,
    connection: RemoteConnection,
    num_shots: int | None = None,
) -> None:
    """Instantiates a QPU backend."""
    self._backend_type = QPUBackend
    self._connection = self.validate_connection(connection)
    if num_shots is None:
        raise ValueError(
            """Number of shots must be provided to use the QPU backend.
            Please specify `num_shots` when creating the QPU instance.
            For example: QPU(connection=..., num_shots=100)""",
        )
    self._config = BackendConfig(default_num_shots=num_shots)
run(program: QuantumProgram) -> Sequence[Results]

Execute a compiled quantum program on the QPU and return the results.

This method submits the program and waits for completion before returning the final results.

Parameters:

  • program (QuantumProgram) –

    The compiled quantum program to execute on the QPU.

Returns:

  • Sequence[Results]

    The execution results from the QPU.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
    """Execute a compiled quantum program on the QPU and return the results.

    This method submits the program and waits for completion before returning
    the final results.

    Args:
        program: The compiled quantum program to execute on the QPU.

    Returns:
        The execution results from the QPU.
    """
    remote_results = self.submit(program, wait=True)
    res_seq: Sequence[Results] = remote_results.results
    return res_seq
submit(program: QuantumProgram, wait: bool = False) -> RemoteResults

Submit a compiled quantum program to the QPU and return a result handler.

Parameters:

  • program (QuantumProgram) –

    The compiled quantum program to execute on the QPU.

  • wait (bool, default: False ) –

    Whether to wait for the QPU to complete execution before returning.

Returns:

  • RemoteResults

    A remote result handler for monitoring job status and retrieving results.

Note

The returned RemoteResults object provides: - Job status monitoring via get_batch_status() - Result retrieval via the results property (when job is complete)

Source code in qoolqit/execution/backends.py
def submit(self, program: QuantumProgram, wait: bool = False) -> RemoteResults:
    """Submit a compiled quantum program to the QPU and return a result handler.

    Args:
        program: The compiled quantum program to execute on the QPU.
        wait: Whether to wait for the QPU to complete execution before returning.

    Returns:
        A remote result handler for monitoring job status and retrieving results.

    Note:
        The returned RemoteResults object provides:
        - Job status monitoring via `get_batch_status()`
        - Result retrieval via the `results` property (when job is complete)
    """
    self._backend = self._backend_type(
        program.compiled_sequence, connection=self._connection, config=self._config
    )
    remote_results = self._backend.run(wait=wait)
    return remote_results
validate_connection(connection: RemoteConnection) -> RemoteConnection staticmethod

Validate the required connection to instantiate a RemoteBackend.

Remote emulators and QPUs require a pulser.backend.remote.RemoteConnection or derived to send jobs. Validation also happens inside the backend. Early validation just makes the error easier to understand.

Source code in qoolqit/execution/backends.py
@staticmethod
def validate_connection(connection: RemoteConnection) -> RemoteConnection:
    """Validate the required connection to instantiate a RemoteBackend.

    Remote emulators and QPUs require a `pulser.backend.remote.RemoteConnection` or derived
    to send jobs. Validation also happens inside the backend. Early validation just makes the
    error easier to understand.
    """
    if not isinstance(connection, RemoteConnection):
        raise TypeError(f"""Error in `PulserRemoteBackend`:
            `connection` must be of type {RemoteConnection}.""")
    return connection
RemoteEmulator(*, backend_type: type[RemoteEmulatorBackend] = EmuFreeBackendV2, connection: RemoteConnection, emulation_config: EmulationConfig | None = None, num_shots: int | None = None)

Run QoolQit QuantumPrograms on a Pasqal remote emulator backends.

This class serves as a primary interface between tools written using QoolQit (including solvers) and remote emulator backends. The behavior is similar to LocalEmulator, but here, requires credentials through a connection to submit/run a program. To get your credentials and to create a connection object, please refer to the Pasqal Cloud interface documentation.

Parameters:

  • backend_type
    (type, default: EmuFreeBackendV2 ) –

    backend type. Must be a subtype of pulser_pasqal.backends.RemoteEmulatorBackend.

  • connection
    (RemoteConnection) –

    connection to execute the program on remote backends.

  • emulation_config
    (EmulationConfig, default: None ) –

    optional configuration object emulators.

  • num_shots
    (int, default: None ) –

    number of bitstring samples to collect from the final quantum state.

Examples:

from pulser_pasqal import PasqalCloud
from qoolqit.execution import RemoteEmulator, BackendType
connection = PasqalCloud(username=..., password=..., project_id=...)
backend = RemoteEmulator(backend_type=BackendType.EmuFreeBackendV2, connection=connection)
then
remote_results = backend.submit(program)
or
results = backend.run(program)

Methods:

  • default_emulation_config

    Return a unique emulation config for all emulators.

  • run

    Run a compiled QuantumProgram remotely and return the results.

  • submit

    Submit a compiled QuantumProgram and return a remote handler of the results.

  • validate_connection

    Validate the required connection to instantiate a RemoteBackend.

  • validate_emulation_config

    Returns a valid config for emulator backends, if needed.

Source code in qoolqit/execution/backends.py
def __init__(
    self,
    *,
    backend_type: type[RemoteEmulatorBackend] = EmuFreeBackendV2,
    connection: RemoteConnection,
    emulation_config: EmulationConfig | None = None,
    num_shots: int | None = None,
) -> None:
    """Instantiates a RemoteEmulator."""
    super().__init__(num_shots=num_shots)
    if not issubclass(backend_type, RemoteEmulatorBackend):
        raise TypeError(
            "Error in `RemoteEmulator`: `backend_type` must be a RemoteEmulatorBackend type."
        )
    self._backend_type = backend_type
    self._connection = self.validate_connection(connection)
    self._emulation_config = self.validate_emulation_config(emulation_config)
default_emulation_config() -> EmulationConfig

Return a unique emulation config for all emulators.

Defaults to a configuration that asks for the final bitstring, sampled num_shots times.

Source code in qoolqit/execution/backends.py
def default_emulation_config(self) -> EmulationConfig:
    """Return a unique emulation config for all emulators.

    Defaults to a configuration that asks for the final bitstring, sampled `num_shots` times.
    """
    return EmulationConfig(observables=(BitStrings(num_shots=self._num_shots),))
run(program: QuantumProgram) -> Sequence[Results]

Run a compiled QuantumProgram remotely and return the results.

Source code in qoolqit/execution/backends.py
def run(self, program: QuantumProgram) -> Sequence[Results]:
    """Run a compiled QuantumProgram remotely and return the results."""
    remote_results = self.submit(program, wait=True)
    res_seq: Sequence[Results] = remote_results.results
    return res_seq
submit(program: QuantumProgram, wait: bool = False) -> RemoteResults

Submit a compiled QuantumProgram and return a remote handler of the results.

The returned handler RemoteResults can be used to: - query the job status with remote_results.get_batch_status() - when DONE, retrieve results with remote_results.results

Parameters:

  • program (QuantumProgram) –

    the compiled quantum program to run.

  • wait (bool, default: False ) –

    Wait for remote backend to complete the job.

Source code in qoolqit/execution/backends.py
def submit(self, program: QuantumProgram, wait: bool = False) -> RemoteResults:
    """Submit a compiled QuantumProgram and return a remote handler of the results.

    The returned handler `RemoteResults` can be used to:
    - query the job status with `remote_results.get_batch_status()`
    - when DONE, retrieve results with `remote_results.results`

    Args:
        program (QuantumProgram): the compiled quantum program to run.
        wait (bool): Wait for remote backend to complete the job.
    """
    # Instantiate backend
    self._backend = self._backend_type(
        program.compiled_sequence,
        connection=self._connection,
        config=self._emulation_config,
    )
    remote_results = self._backend.run(wait=wait)
    return remote_results
validate_connection(connection: RemoteConnection) -> RemoteConnection staticmethod

Validate the required connection to instantiate a RemoteBackend.

Remote emulators and QPUs require a pulser.backend.remote.RemoteConnection or derived to send jobs. Validation also happens inside the backend. Early validation just makes the error easier to understand.

Source code in qoolqit/execution/backends.py
@staticmethod
def validate_connection(connection: RemoteConnection) -> RemoteConnection:
    """Validate the required connection to instantiate a RemoteBackend.

    Remote emulators and QPUs require a `pulser.backend.remote.RemoteConnection` or derived
    to send jobs. Validation also happens inside the backend. Early validation just makes the
    error easier to understand.
    """
    if not isinstance(connection, RemoteConnection):
        raise TypeError(f"""Error in `PulserRemoteBackend`:
            `connection` must be of type {RemoteConnection}.""")
    return connection
validate_emulation_config(emulation_config: EmulationConfig | None) -> EmulationConfig

Returns a valid config for emulator backends, if needed.

Parameters:

  • emulation_config (EmulationConfig | None) –

    optional base configuration class for all emulators backends. If no config is provided to an emulator backend, the backend default will used.

Source code in qoolqit/execution/backends.py
def validate_emulation_config(
    self, emulation_config: EmulationConfig | None
) -> EmulationConfig:
    """Returns a valid config for emulator backends, if needed.

    Args:
        emulation_config: optional base configuration class for all emulators backends.
            If no config is provided to an emulator backend, the backend default will used.
    """
    if emulation_config is None:
        emulation_config = self.default_emulation_config()
    else:
        has_bitstrings = any(
            isinstance(obs, BitStrings) for obs in emulation_config.observables
        )
        if not has_bitstrings:
            updated_obs = (*emulation_config.observables, BitStrings(num_shots=self._num_shots))
            emulation_config = emulation_config.with_changes(observables=updated_obs)

    if self._num_shots is not None:
        emulation_config = emulation_config.with_changes(default_num_shots=self._num_shots)

    return emulation_config

compilation_functions

Classes:

Functions:

WaveformConverter(device: Device, time: float, energy: float)

Convert a QoolQit waveform into a equivalent Pulser waveform.

Requires the new time and energy scales set by the compilation. Additionally, requires the clock period of the device to round the duration.

Methods:

  • convert

    Convert a QoolQit waveform into a equivalent Pulser waveform.

Source code in qoolqit/execution/compilation_functions.py
def __init__(self, device: Device, time: float, energy: float) -> None:
    self._time = time
    self._energy = energy
    self._clock_period = device._clock_period
convert(waveform: Waveform) -> ParamObj | PulserWaveform

Convert a QoolQit waveform into a equivalent Pulser waveform.

Source code in qoolqit/execution/compilation_functions.py
def convert(self, waveform: Waveform) -> ParamObj | PulserWaveform:
    """Convert a QoolQit waveform into a equivalent Pulser waveform."""
    pulser_duration = self._pulser_duration(waveform)
    return waveform._to_pulser(duration=pulser_duration) * self._energy
basic_compilation(register: Register, drive: Drive, device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None) -> PulserSequence

Compiles a QoolQit program to a PulserSequence.

Defines: - program_energy_ratio: the ratio between the maximum amplitude in the drive and the maximum interaction energy. - device_energy_ratio: the ratio between the device's maximum allowed amplitude in the drive and the maximum possible interaction energy.

If program_energy_ratio > device_energy_ratio the program is scaled to match the device's maximum allowed amplitude. Otherwise, the program is scaled to match the device's minimum allowed pairwise distance.

If the device requires a layout, it is automatically generated.

Parameters:

  • register
    (Register) –

    QoolQit Register.

  • drive
    (Drive) –

    QoolQit Drive.

  • device
    (Device) –

    QoolQit Device.

  • device_max_duration_ratio
    (float | None, default: None ) –

    optionally set the program duration to a fraction of the device's maximum allowed duration.

Returns:

  • PulserSequence ( Sequence ) –

    The compiled program as a pulser.Sequence object.

Source code in qoolqit/execution/compilation_functions.py
def basic_compilation(
    register: Register,
    drive: Drive,
    device: Device,
    profile: CompilerProfile = CompilerProfile.MAX_ENERGY,
    device_max_duration_ratio: float | None = None,
) -> PulserSequence:
    """Compiles a QoolQit program to a PulserSequence.

    Defines:
    - program_energy_ratio: the ratio between the maximum amplitude in the drive
        and the maximum interaction energy.
    - device_energy_ratio: the ratio between the device's maximum allowed amplitude in the drive
        and the maximum possible interaction energy.

    If program_energy_ratio > device_energy_ratio the program is scaled to match the device's
    maximum allowed amplitude.
    Otherwise, the program is scaled to match the device's minimum allowed pairwise distance.

    If the device requires a layout, it is automatically generated.

    Args:
        register: QoolQit Register.
        drive: QoolQit Drive.
        device: QoolQit Device.
        device_max_duration_ratio: optionally set the program duration to a fraction
            of the device's maximum allowed duration.

    Returns:
        PulserSequence: The compiled program as a pulser.Sequence object.
    """
    if profile == CompilerProfile.WORKING_POINT:
        TIME, ENERGY, DISTANCE = device.converter.factors
        _validate_program_default_profile(register, drive, device)
    elif profile == CompilerProfile.MAX_ENERGY:
        # fix compilation strategy according to the program energy ratio Ω_max/J_max
        program_energy_ratio = drive.amplitude.max() * register.min_distance() ** 6
        device_energy_ratio = device._energy_ratio
        if program_energy_ratio > device_energy_ratio:
            # map to the maximum amplitude allowed on the device
            ENERGY = device._target_amp / drive.amplitude.max()
            TIME, ENERGY, DISTANCE = device.converter.factors_from_energy(ENERGY)
        else:
            # map to the minimum pairwise distance allowed on the device
            DISTANCE = device._target_dist / register.min_distance()
            TIME, ENERGY, DISTANCE = device.converter.factors_from_distance(DISTANCE)
        _validate_program_max_energy_profile(register, drive, device)
    else:
        raise ValueError(f"Invalid CompilerProfile: {profile}")

    # if device_max_duration_ratio is set and the device has max_duration
    # set the duration to the fraction of device's maximum duration.
    if device_max_duration_ratio and device._max_duration:
        TIME = device_max_duration_ratio * device._max_duration / drive.duration

    # Build pulser pulse and register
    wf_converter = WaveformConverter(device=device, time=TIME, energy=ENERGY)
    pulser_amp_wf = wf_converter.convert(drive._amplitude)
    pulser_det_wf = wf_converter.convert(drive._detuning)
    pulser_pulse = PulserPulse(pulser_amp_wf, pulser_det_wf, drive.phase)

    pulser_register = _build_register(register, device, DISTANCE)

    # Create sequence
    pulser_device = device._device
    pulser_sequence = PulserSequence(pulser_register, pulser_device)
    pulser_sequence.declare_channel("rydberg", "rydberg_global")
    pulser_sequence.add(pulser_pulse, "rydberg")

    if len(drive.weighted_detunings) > 0:
        # Add detuning map
        channels = list(device._device.dmm_channels.keys())
        if len(channels) == 0:
            raise ValueError(
                f"This program specifies {len(drive.weighted_detunings)} detunings but "
                "the device doesn't offer any DMM channel to execute them."
            )

        detuning_adder = _DetuningAdder(wf_converter, pulser_register, pulser_sequence)

        # If our device supports reusable channels, we can declare multiple
        # DMM channels with the same specs
        if pulser_device.reusable_channels:
            # Arbitrarily pick the first channel.
            dmm_id = channels[0]
            for detuning in drive.weighted_detunings:
                detuning_adder.add_detuning(dmm_id, detuning)
        # Do we have enough channels for our detunings?
        elif len(channels) >= len(drive.weighted_detunings):
            for dmm_id, detuning in zip(channels, drive.weighted_detunings):
                detuning_adder.add_detuning(dmm_id, detuning)
        else:
            raise ValueError(
                f"This program specifies {len(drive.weighted_detunings)} detunings but "
                f"the device only offers {len(channels)} DMM channels to execute them."
            )

    return pulser_sequence

sequence_compiler

Classes:

SequenceCompiler(register: Register, drive: Drive, device: Device, profile: CompilerProfile, device_max_duration_ratio: float | None = None)

Compiles a QoolQit Register and Drive to a Device.

Parameters:

  • register
    (Register) –

    the QoolQit Register.

  • drive
    (Drive) –

    the QoolQit Drive.

  • device
    (Device) –

    the QoolQit Device.

  • profile
    (CompilerProfile) –

    the CompilerProfile to use.

  • device_max_duration_ratio
    (float | None, default: None ) –

    optionally set the program duration to a fraction of the device's maximum allowed duration.

Source code in qoolqit/execution/sequence_compiler.py
def __init__(
    self,
    register: Register,
    drive: Drive,
    device: Device,
    profile: CompilerProfile,
    device_max_duration_ratio: float | None = None,
) -> None:
    """Initializes the compiler.

    Args:
        register: the QoolQit Register.
        drive: the QoolQit Drive.
        device: the QoolQit Device.
        profile: the CompilerProfile to use.
        device_max_duration_ratio: optionally set the program duration to a fraction
            of the device's maximum allowed duration.
    """

    self._register = register
    self._drive = drive
    self._device = device
    self._target_device = device._device
    self._profile = profile
    self._device_max_duration_ratio = device_max_duration_ratio
    self._compilation_function: Callable = basic_compilation

graphs

Graph creation and manipulation in QoolQit.

Modules:

Classes:

  • BaseGraph

    The BaseGraph in QoolQit, directly inheriting from the NetworkX Graph.

  • DataGraph

    The main graph structure to represent problem data.

Functions:

  • all_node_pairs

    Return all pairs of nodes (u, v) where u < v.

  • distances

    Return a dictionary of edge distances.

  • random_coords

    Generate a random set of node coordinates on a square of side L.

  • random_edge_list

    Generates a random set of k edges linkings items from a set of nodes.

  • scale_coords

    Scale the coordinates by a given value.

  • space_coords

    Spaces the coordinates so the minimum distance is equal to a set spacing.

BaseGraph(edges: Iterable = [])

The BaseGraph in QoolQit, directly inheriting from the NetworkX Graph.

Defines basic functionalities for graphs within the Rydberg Analog, such as instantiating from a set of node coordinates, directly accessing node distances, and checking if the graph is unit-disk.

Parameters:

  • edges
    (Iterable, default: [] ) –

    set of edge tuples (i, j)

Methods:

  • distances

    Returns a dictionary of distances for a given set of edges.

  • draw

    Draw the graph.

  • from_coordinates

    Construct a base graph from a set of coordinates.

  • from_nodes

    Construct a base graph from a set of nodes.

  • from_nx

    Convert a NetworkX Graph object into a QoolQit graph instance.

  • interactions

    Rydberg model interaction 1/r^6 between pair of nodes.

  • is_ud_graph

    Check if the graph is unit-disk.

  • max_distance

    Returns the maximum distance in the graph.

  • min_distance

    Returns the minimum distance in the graph.

  • rescale_coords

    Rescales the node coordinates by a factor.

  • set_ud_edges

    Reset the set of edges to be equal to the set of unit-disk edges.

  • ud_edges

    Returns the set of edges given by the intersection of circles of a given radius.

  • ud_radius_range

    Return the range (R_min, R_max) where the graph is unit-disk.

Attributes:

  • all_node_pairs (set) –

    Return a list of all possible node pairs in the graph.

  • coords (dict) –

    Return the dictionary of node coordinates.

  • has_coords (bool) –

    Check if the graph has coordinates.

  • has_edge_weights (bool) –

    Check if the graph has edge weights.

  • has_edges (bool) –

    Check if the graph has edges.

  • has_node_weights (bool) –

    Check if the graph has node weights.

  • sorted_edges (set) –

    Returns the set of edges (u, v) such that (u < v).

Source code in qoolqit/graphs/base_graph.py
def __init__(self, edges: Iterable = []) -> None:
    """
    Default constructor for the BaseGraph.

    Arguments:
        edges: set of edge tuples (i, j)
    """
    if edges and not isinstance(edges, Iterable):
        raise TypeError("Input is not a valid edge list.")

    super().__init__()
    self.add_edges_from(edges)
    self._coords = {i: None for i in self.nodes}
    self._reset_dicts()
all_node_pairs: set property

Return a list of all possible node pairs in the graph.

coords: dict property writable

Return the dictionary of node coordinates.

has_coords: bool property

Check if the graph has coordinates.

Requires all nodes to have coordinates.

has_edge_weights: bool property

Check if the graph has edge weights.

Requires all edges to have a weight.

has_edges: bool property

Check if the graph has edges.

has_node_weights: bool property

Check if the graph has node weights.

Requires all nodes to have a weight.

sorted_edges: set property

Returns the set of edges (u, v) such that (u < v).

distances(edge_list: Iterable | None = None) -> dict

Returns a dictionary of distances for a given set of edges.

Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.

Parameters:

  • edge_list
    (Iterable | None, default: None ) –

    set of edges.

Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict:
    """Returns a dictionary of distances for a given set of edges.

    Distances are calculated directly from the coordinates. Raises an error
    if there are no coordinates on the graph.

    Arguments:
        edge_list: set of edges.
    """
    if self.has_coords:
        if edge_list is None:
            edge_list = self.all_node_pairs
        elif len(edge_list) == 0:  # type: ignore [arg-type]
            raise ValueError("Trying to compute distances for an empty edge list.")
        return distances(self.coords, edge_list)
    else:
        raise AttributeError("Trying to compute distances for a graph without coordinates.")
draw(ax: Axes | None = None, **kwargs: Any) -> None

Draw the graph.

Uses the draw_networkx function from NetworkX.

Parameters:

  • ax
    (Axes | None, default: None ) –

    Axes object to draw on. If None, uses the current Axes.

  • **kwargs
    (Any, default: {} ) –

    keyword-arguments to pass to draw_networkx.

Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None:
    """Draw the graph.

    Uses the draw_networkx function from NetworkX.

    Args:
        ax: Axes object to draw on. If None, uses the current Axes.
        **kwargs: keyword-arguments to pass to draw_networkx.
    """
    if self.has_coords:
        if "hide_ticks" not in kwargs:
            kwargs["hide_ticks"] = False

        nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)

        if ax is None:
            ax = plt.gca()
        ax.set_xlabel("x")
        ax.set_ylabel("y")
        ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)

        # minimum ybox
        ylim = ax.get_ylim()
        if (ylim[1] - ylim[0]) < 2:
            y_center = (ylim[0] + ylim[1]) / 2
            ax.set_ylim(y_center - 1, y_center + 1)
        plt.tight_layout()
    else:
        nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph classmethod

Construct a base graph from a set of coordinates.

Parameters:

  • coords
    (list | dict) –

    list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_coordinates(cls, coords: list | dict) -> BaseGraph:
    """Construct a base graph from a set of coordinates.

    Arguments:
        coords: list or dictionary of coordinate pairs.
    """
    if isinstance(coords, list):
        nodes = list(range(len(coords)))
        coords_dict = {i: pos for i, pos in enumerate(coords)}
    elif isinstance(coords, dict):
        nodes = list(coords.keys())
        coords_dict = coords
    graph = cls.from_nodes(nodes)
    graph._coords = coords_dict
    graph._reset_dicts()
    return graph
from_nodes(nodes: Iterable) -> BaseGraph classmethod

Construct a base graph from a set of nodes.

Parameters:

  • nodes
    (Iterable) –

    set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nodes(cls, nodes: Iterable) -> BaseGraph:
    """Construct a base graph from a set of nodes.

    Arguments:
        nodes: set of nodes.
    """
    graph = cls()
    graph.add_nodes_from(nodes)
    graph._coords = {i: None for i in graph.nodes}
    graph._reset_dicts()
    return graph
from_nx(g: nx.Graph) -> BaseGraph classmethod

Convert a NetworkX Graph object into a QoolQit graph instance.

The input networkx.Graph graph must be defined only with the following allowed

Node attributes

pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers. weight: represents the node weight. Must be a real number.

Edge attributes: weight: represents the edge weight. Must be a real number.

Returns an instance of the class with following attributes
  • _node_weights : dict[node, float or None]
  • _edge_weights : dict[(u,v), float or None]
  • _coords : dict[node, (float,float) or None]
Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nx(cls, g: nx.Graph) -> BaseGraph:
    """Convert a NetworkX Graph object into a QoolQit graph instance.

    The input `networkx.Graph` graph must be defined only with the following allowed

    Node attributes:
        pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers.
        weight: represents the node weight. Must be a real number.
    Edge attributes:
        weight: represents the edge weight. Must be a real number.

    Returns an instance of the class with following attributes:
        - _node_weights : dict[node, float or None]
        - _edge_weights : dict[(u,v), float or None]
        - _coords       : dict[node, (float,float) or None]
    """
    if not isinstance(g, nx.Graph):
        raise TypeError("Input must be a networkx.Graph instance.")

    g = nx.convert_node_labels_to_integers(g)
    num_nodes = len(g.nodes)
    num_edges = len(g.edges)

    # validate node attributes
    for name, data in g.nodes.data():
        unexpected_keys = set(data) - {"weight", "pos"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in node attributes.")

    node_pos = nx.get_node_attributes(g, "pos")
    if node_pos:
        if len(node_pos) != num_nodes:
            raise ValueError("Node attribute `pos` must be defined for all nodes")
        for name, pos in node_pos.items():
            is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2)
            is_real = all(isinstance(p, (float, int)) for p in pos)
            if not (is_2D & is_real):
                raise TypeError(
                    f"In node {name} the `pos` attribute must be a 2D tuple/list"
                    f" of real numbers, got {pos} instead."
                )
    node_weights = nx.get_node_attributes(g, "weight")
    if node_weights:
        if len(node_weights) != num_nodes:
            raise ValueError("Node attribute `weight` must be defined for all nodes")
        for name, weight in node_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In node {name} the `weight` attribute must be a real number, "
                    f"got {type(weight)} instead."
                    ""
                )

    # validate edge attributes
    for u, v, data in g.edges.data():
        unexpected_keys = set(data) - {"weight"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in edge attributes.")
    edge_weights = nx.get_edge_attributes(g, "weight")
    if edge_weights:
        if len(edge_weights) != num_edges:
            raise ValueError("Edge attribute `weight` must be defined for all edges")
        for name, weight in edge_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In edge {name}, the attribute `weight` must be a real number, "
                    f"got {type(weight)} instead."
                )

    # build the instance of the graph
    graph = cls()
    graph.add_nodes_from(g.nodes)
    graph.add_edges_from(g.edges)
    graph._node_weights = nx.get_node_attributes(g, "weight", default=None)
    graph._coords = nx.get_node_attributes(g, "pos", default=None)
    graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)

    return graph
interactions() -> dict

Rydberg model interaction 1/r^6 between pair of nodes.

Source code in qoolqit/graphs/base_graph.py
def interactions(self) -> dict:
    """Rydberg model interaction 1/r^6 between pair of nodes."""
    return {p: 1.0 / (r**6) for p, r in self.distances().items()}
is_ud_graph() -> bool

Check if the graph is unit-disk.

Source code in qoolqit/graphs/base_graph.py
def is_ud_graph(self) -> bool:
    """Check if the graph is unit-disk."""
    try:
        self.ud_radius_range()
        return True
    except ValueError:
        return False
max_distance(connected: bool | None = None) -> float

Returns the maximum distance in the graph.

Parameters:

  • connected
    (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float:
    """Returns the maximum distance in the graph.

    Arguments:
        connected: if True/False, computes only over connected/disconnected nodes.
    """
    distance: float
    if connected is None:
        distance = max(self.distances(self.all_node_pairs).values())
    elif connected:
        distance = max(self.distances(self.sorted_edges).values())
    else:
        distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values())
    return distance
min_distance(connected: bool | None = None) -> float

Returns the minimum distance in the graph.

Parameters:

  • connected
    (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float:
    """Returns the minimum distance in the graph.

    Arguments:
        connected: if True/False, computes only over connected/disconnected nodes.
    """
    distance: float
    if connected is None:
        distance = min(self.distances(self.all_node_pairs).values())
    elif connected:
        distance = min(self.distances(self.sorted_edges).values())
    else:
        distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values())
    return distance
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

  • scaling
    (float | None, default: None ) –

    value to scale by.

  • spacing
    (float | None, default: None ) –

    value to set as the minimum distance in the graph.

Source code in qoolqit/graphs/base_graph.py
def rescale_coords(
    self,
    *args: Any,
    scaling: float | None = None,
    spacing: float | None = None,
) -> None:
    """Rescales the node coordinates by a factor.

    Accepts either a scaling or a spacing factor.

    Arguments:
        scaling: value to scale by.
        spacing: value to set as the minimum distance in the graph.
    """
    if self.has_coords:
        msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument."
        if (len(args) > 0) or (scaling is None and spacing is None):
            raise TypeError(msg)
        if scaling is None and spacing is not None:
            self._coords = space_coords(self._coords, spacing)
        elif spacing is None and scaling is not None:
            self._coords = scale_coords(self._coords, scaling)
        else:
            raise TypeError(msg)
    else:
        raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
set_ud_edges(radius: float) -> None

Reset the set of edges to be equal to the set of unit-disk edges.

Parameters:

  • radius
    (float) –

    the radius to use in determining the set of unit-disk edges.

Source code in qoolqit/graphs/base_graph.py
def set_ud_edges(self, radius: float) -> None:
    """Reset the set of edges to be equal to the set of unit-disk edges.

    Arguments:
        radius: the radius to use in determining the set of unit-disk edges.
    """
    self.remove_edges_from(list(self.edges))
    self.add_edges_from(self.ud_edges(radius))
ud_edges(radius: float) -> set

Returns the set of edges given by the intersection of circles of a given radius.

Parameters:

  • radius
    (float) –

    the value

Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set:
    """Returns the set of edges given by the intersection of circles of a given radius.

    Arguments:
        radius: the value
    """
    if self.has_coords:
        return set(e for e, d in self.distances().items() if less_or_equal(d, radius))
    else:
        raise AttributeError("Getting unit disk edges is not valid without coordinates.")
ud_radius_range() -> tuple

Return the range (R_min, R_max) where the graph is unit-disk.

The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:

graph.ud_edges(radius = R) == graph.sorted edges

Source code in qoolqit/graphs/base_graph.py
def ud_radius_range(self) -> tuple:
    """Return the range (R_min, R_max) where the graph is unit-disk.

    The graph is unit-disk if the maximum distance between all connected nodes is
    smaller than the minimum distance between disconnected nodes. This means that
    for any value R in that interval, the following condition is true:

    graph.ud_edges(radius = R) == graph.sorted edges
    """
    if self.has_coords:
        n_edges = len(self.sorted_edges)
        if n_edges == 0:
            # If the graph is empty and has coordinates
            return (0.0, self.min_distance(connected=False))
        elif n_edges == len(self.all_node_pairs):
            # If the graph is fully connected
            return (self.max_distance(connected=True), float("inf"))
        elif self.max_distance(connected=True) < self.min_distance(connected=False):
            return (self.max_distance(connected=True), self.min_distance(connected=False))
        else:
            raise ValueError("Graph is not unit disk.")
    else:
        raise AttributeError("Checking if graph is unit disk is not valid without coordinates.")

DataGraph(edges: Iterable = [])

The main graph structure to represent problem data.

Parameters:

  • edges
    (Iterable, default: [] ) –

    set of edge tuples (i, j)

Methods:

  • circle

    Constructs a circle graph, with the respective coordinates.

  • distances

    Returns a dictionary of distances for a given set of edges.

  • draw

    Draw the graph.

  • from_coordinates

    Construct a base graph from a set of coordinates.

  • from_matrix

    Constructs a graph from a symmetric square matrix.

  • from_nodes

    Construct a base graph from a set of nodes.

  • from_nx

    Convert a NetworkX Graph object into a QoolQit graph instance.

  • from_pyg

    Convert a PyTorch Geometric Data object into a DataGraph instance.

  • heavy_hexagonal

    Constructs a heavy-hexagonal lattice graph, with respective coordinates.

  • hexagonal

    Constructs a hexagonal lattice graph, with respective coordinates.

  • interactions

    Rydberg model interaction 1/r^6 between pair of nodes.

  • is_ud_graph

    Check if the graph is unit-disk.

  • line

    Constructs a line graph, with the respective coordinates.

  • max_distance

    Returns the maximum distance in the graph.

  • min_distance

    Returns the minimum distance in the graph.

  • random_er

    Constructs an Erdős–Rényi random graph.

  • random_ud

    Constructs a random unit-disk graph.

  • rescale_coords

    Rescales the node coordinates by a factor.

  • set_ud_edges

    Reset the set of edges to be equal to the set of unit-disk edges.

  • square

    Constructs a square lattice graph, with respective coordinates.

  • to_pyg

    Convert the DataGraph to a PyTorch Geometric Data object.

  • triangular

    Constructs a triangular lattice graph, with respective coordinates.

  • ud_edges

    Returns the set of edges given by the intersection of circles of a given radius.

  • ud_radius_range

    Return the range (R_min, R_max) where the graph is unit-disk.

Attributes:

  • all_node_pairs (set) –

    Return a list of all possible node pairs in the graph.

  • coords (dict) –

    Return the dictionary of node coordinates.

  • edge_weights (dict) –

    Return the dictionary of edge weights.

  • has_coords (bool) –

    Check if the graph has coordinates.

  • has_edge_weights (bool) –

    Check if the graph has edge weights.

  • has_edges (bool) –

    Check if the graph has edges.

  • has_node_weights (bool) –

    Check if the graph has node weights.

  • node_weights (dict) –

    Return the dictionary of node weights.

  • sorted_edges (set) –

    Returns the set of edges (u, v) such that (u < v).

Source code in qoolqit/graphs/data_graph.py
def __init__(self, edges: Iterable = []) -> None:
    """
    Default constructor for the BaseGraph.

    Arguments:
        edges: set of edge tuples (i, j)
    """
    super().__init__(edges)
all_node_pairs: set property

Return a list of all possible node pairs in the graph.

coords: dict property writable

Return the dictionary of node coordinates.

edge_weights: dict property writable

Return the dictionary of edge weights.

has_coords: bool property

Check if the graph has coordinates.

Requires all nodes to have coordinates.

has_edge_weights: bool property

Check if the graph has edge weights.

Requires all edges to have a weight.

has_edges: bool property

Check if the graph has edges.

has_node_weights: bool property

Check if the graph has node weights.

Requires all nodes to have a weight.

node_weights: dict property writable

Return the dictionary of node weights.

sorted_edges: set property

Returns the set of edges (u, v) such that (u < v).

circle(n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0)) -> DataGraph classmethod

Constructs a circle graph, with the respective coordinates.

Parameters:

  • n
    (int) –

    number of nodes.

  • spacing
    (float, default: 1.0 ) –

    distance between each node.

  • center
    (tuple, default: (0.0, 0.0) ) –

    point (x, y) to set as the center of the graph.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def circle(
    cls,
    n: int,
    spacing: float = 1.0,
    center: tuple = (0.0, 0.0),
) -> DataGraph:
    """Constructs a circle graph, with the respective coordinates.

    Arguments:
        n: number of nodes.
        spacing: distance between each node.
        center: point (x, y) to set as the center of the graph.
    """

    d_theta = (2.0 * np.pi) / n
    r = spacing / (2.0 * np.sin(np.pi / n))
    theta = np.linspace(0.0, 2.0 * np.pi - d_theta, n)
    coords = [
        (x + center[0], y + center[1]) for x, y in zip(r * np.cos(theta), r * np.sin(theta))
    ]
    edges = [(i, i + 1) for i in range(n - 1)] + [(n - 1, 0)]
    graph = cls.from_coordinates(coords)
    graph.add_edges_from(edges)
    graph._reset_dicts()
    return graph
distances(edge_list: Iterable | None = None) -> dict

Returns a dictionary of distances for a given set of edges.

Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.

Parameters:

  • edge_list
    (Iterable | None, default: None ) –

    set of edges.

Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict:
    """Returns a dictionary of distances for a given set of edges.

    Distances are calculated directly from the coordinates. Raises an error
    if there are no coordinates on the graph.

    Arguments:
        edge_list: set of edges.
    """
    if self.has_coords:
        if edge_list is None:
            edge_list = self.all_node_pairs
        elif len(edge_list) == 0:  # type: ignore [arg-type]
            raise ValueError("Trying to compute distances for an empty edge list.")
        return distances(self.coords, edge_list)
    else:
        raise AttributeError("Trying to compute distances for a graph without coordinates.")
draw(ax: Axes | None = None, **kwargs: Any) -> None

Draw the graph.

Uses the draw_networkx function from NetworkX.

Parameters:

  • ax
    (Axes | None, default: None ) –

    Axes object to draw on. If None, uses the current Axes.

  • **kwargs
    (Any, default: {} ) –

    keyword-arguments to pass to draw_networkx.

Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None:
    """Draw the graph.

    Uses the draw_networkx function from NetworkX.

    Args:
        ax: Axes object to draw on. If None, uses the current Axes.
        **kwargs: keyword-arguments to pass to draw_networkx.
    """
    if self.has_coords:
        if "hide_ticks" not in kwargs:
            kwargs["hide_ticks"] = False

        nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)

        if ax is None:
            ax = plt.gca()
        ax.set_xlabel("x")
        ax.set_ylabel("y")
        ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)

        # minimum ybox
        ylim = ax.get_ylim()
        if (ylim[1] - ylim[0]) < 2:
            y_center = (ylim[0] + ylim[1]) / 2
            ax.set_ylim(y_center - 1, y_center + 1)
        plt.tight_layout()
    else:
        nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph classmethod

Construct a base graph from a set of coordinates.

Parameters:

  • coords
    (list | dict) –

    list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_coordinates(cls, coords: list | dict) -> BaseGraph:
    """Construct a base graph from a set of coordinates.

    Arguments:
        coords: list or dictionary of coordinate pairs.
    """
    if isinstance(coords, list):
        nodes = list(range(len(coords)))
        coords_dict = {i: pos for i, pos in enumerate(coords)}
    elif isinstance(coords, dict):
        nodes = list(coords.keys())
        coords_dict = coords
    graph = cls.from_nodes(nodes)
    graph._coords = coords_dict
    graph._reset_dicts()
    return graph
from_matrix(data: ArrayLike) -> DataGraph classmethod

Constructs a graph from a symmetric square matrix.

The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.

Parameters:

  • data
    (ArrayLike) –

    symmetric square matrix.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def from_matrix(cls, data: ArrayLike) -> DataGraph:
    """Constructs a graph from a symmetric square matrix.

    The diagonal values are set as the node weights. For each entry (i, j)
    where M[i, j] != 0 an edge (i, j) is added to the graph and the value
    M[i, j] is set as its weight.

    Arguments:
        data: symmetric square matrix.
    """
    if data.ndim != 2:
        raise ValueError("2D Matrix required.")
    if not np.allclose(data, data.T, rtol=0.0, atol=ATOL_32):
        raise ValueError("Matrix must be symmetric.")

    diag = np.diag(data)
    n_nodes = len(diag)
    node_weights = {i: diag[i] for i in range(n_nodes)}
    if np.allclose(diag, np.zeros(n_nodes), rtol=0.0, atol=ATOL_32):
        node_weights = {i: None for i in range(n_nodes)}
    else:
        node_weights = {i: diag[i].item() for i in range(n_nodes)}

    data[data <= ATOL_32] = 0.0
    non_zero = data.nonzero()
    i_list = non_zero[0].tolist()
    j_list = non_zero[1].tolist()

    edge_list = [(i, j) for i, j in zip(i_list, j_list) if i < j]
    edge_weights = {(i, j): data[i, j].item() for i, j in edge_list}

    graph = cls.from_nodes(range(n_nodes))
    graph.add_edges_from(edge_list)
    graph.node_weights = node_weights
    graph.edge_weights = edge_weights
    return graph
from_nodes(nodes: Iterable) -> BaseGraph classmethod

Construct a base graph from a set of nodes.

Parameters:

  • nodes
    (Iterable) –

    set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nodes(cls, nodes: Iterable) -> BaseGraph:
    """Construct a base graph from a set of nodes.

    Arguments:
        nodes: set of nodes.
    """
    graph = cls()
    graph.add_nodes_from(nodes)
    graph._coords = {i: None for i in graph.nodes}
    graph._reset_dicts()
    return graph
from_nx(g: nx.Graph) -> BaseGraph classmethod

Convert a NetworkX Graph object into a QoolQit graph instance.

The input networkx.Graph graph must be defined only with the following allowed

Node attributes

pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers. weight: represents the node weight. Must be a real number.

Edge attributes: weight: represents the edge weight. Must be a real number.

Returns an instance of the class with following attributes
  • _node_weights : dict[node, float or None]
  • _edge_weights : dict[(u,v), float or None]
  • _coords : dict[node, (float,float) or None]
Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nx(cls, g: nx.Graph) -> BaseGraph:
    """Convert a NetworkX Graph object into a QoolQit graph instance.

    The input `networkx.Graph` graph must be defined only with the following allowed

    Node attributes:
        pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers.
        weight: represents the node weight. Must be a real number.
    Edge attributes:
        weight: represents the edge weight. Must be a real number.

    Returns an instance of the class with following attributes:
        - _node_weights : dict[node, float or None]
        - _edge_weights : dict[(u,v), float or None]
        - _coords       : dict[node, (float,float) or None]
    """
    if not isinstance(g, nx.Graph):
        raise TypeError("Input must be a networkx.Graph instance.")

    g = nx.convert_node_labels_to_integers(g)
    num_nodes = len(g.nodes)
    num_edges = len(g.edges)

    # validate node attributes
    for name, data in g.nodes.data():
        unexpected_keys = set(data) - {"weight", "pos"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in node attributes.")

    node_pos = nx.get_node_attributes(g, "pos")
    if node_pos:
        if len(node_pos) != num_nodes:
            raise ValueError("Node attribute `pos` must be defined for all nodes")
        for name, pos in node_pos.items():
            is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2)
            is_real = all(isinstance(p, (float, int)) for p in pos)
            if not (is_2D & is_real):
                raise TypeError(
                    f"In node {name} the `pos` attribute must be a 2D tuple/list"
                    f" of real numbers, got {pos} instead."
                )
    node_weights = nx.get_node_attributes(g, "weight")
    if node_weights:
        if len(node_weights) != num_nodes:
            raise ValueError("Node attribute `weight` must be defined for all nodes")
        for name, weight in node_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In node {name} the `weight` attribute must be a real number, "
                    f"got {type(weight)} instead."
                    ""
                )

    # validate edge attributes
    for u, v, data in g.edges.data():
        unexpected_keys = set(data) - {"weight"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in edge attributes.")
    edge_weights = nx.get_edge_attributes(g, "weight")
    if edge_weights:
        if len(edge_weights) != num_edges:
            raise ValueError("Edge attribute `weight` must be defined for all edges")
        for name, weight in edge_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In edge {name}, the attribute `weight` must be a real number, "
                    f"got {type(weight)} instead."
                )

    # build the instance of the graph
    graph = cls()
    graph.add_nodes_from(g.nodes)
    graph.add_edges_from(g.edges)
    graph._node_weights = nx.get_node_attributes(g, "weight", default=None)
    graph._coords = nx.get_node_attributes(g, "pos", default=None)
    graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)

    return graph
from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph classmethod

Convert a PyTorch Geometric Data object into a DataGraph instance.

Requires torch_geometric. Uses to_networkx internally.

Default attributes copied (if present on data ):

  • Node: x, pos (pos is also stored in _coords)
  • Edge: edge_attr
  • Graph: y

Use node_attrs, edge_attrs, graph_attrs for extras.

QoolQit weights (_node_weights, _edge_weights) are not populated automatically — use the explicit parameters:

  • node_weights_attr: real-valued tensor of shape (N,) or (N, 1). Defaults to None.
  • edge_weights_attr: real-valued tensor of shape (E,) or (E, 1) where E = edge_index.shape[1] (directed count). Defaults to None.

The weight attribute is also stored as a regular node/edge attribute.

Parameters:

  • data
    (Data) –

    PyTorch Geometric Data object to convert.

  • node_attrs
    (Iterable[str] | None, default: None ) –

    extra node attributes to copy (beyond x and pos).

  • edge_attrs
    (Iterable[str] | None, default: None ) –

    extra edge attributes to copy (beyond edge_attr).

  • graph_attrs
    (Iterable[str] | None, default: None ) –

    extra graph-level attributes to copy (beyond y).

  • node_weights_attr
    (str | None, default: None ) –

    Data attribute to use as node weights.

  • edge_weights_attr
    (str | None, default: None ) –

    Data attribute to use as edge weights.

Returns:

  • DataGraph

    DataGraph with _coords, _node_weights, _edge_weights

  • DataGraph

    populated where applicable.

Raises:

  • ImportError

    if torch_geometric is not installed.

  • TypeError

    if data is not a torch_geometric.data.Data instance, or if a weight attribute is not a torch.Tensor.

  • AttributeError

    if a specified weight attribute is missing.

  • ValueError

    if a weight tensor has an incompatible shape or size.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def from_pyg(
    cls,
    data: torch_geometric.data.Data,
    node_attrs: Iterable[str] | None = None,
    edge_attrs: Iterable[str] | None = None,
    graph_attrs: Iterable[str] | None = None,
    node_weights_attr: str | None = None,
    edge_weights_attr: str | None = None,
) -> DataGraph:
    """Convert a PyTorch Geometric Data object into a DataGraph instance.

    Requires ``torch_geometric``. Uses ``to_networkx`` internally.

    **Default attributes copied (if present on** ``data`` **):**

    - Node: ``x``, ``pos`` (``pos`` is also stored in ``_coords``)
    - Edge: ``edge_attr``
    - Graph: ``y``

    Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.

    **QoolQit weights** (``_node_weights``, ``_edge_weights``) are not
    populated automatically — use the explicit parameters:

    - ``node_weights_attr``: real-valued tensor of shape ``(N,)`` or
      ``(N, 1)``. Defaults to ``None``.
    - ``edge_weights_attr``: real-valued tensor of shape ``(E,)`` or
      ``(E, 1)`` where ``E = edge_index.shape[1]`` (directed count).
      Defaults to ``None``.

    The weight attribute is also stored as a regular node/edge attribute.

    Arguments:
        data: PyTorch Geometric Data object to convert.
        node_attrs: extra node attributes to copy (beyond x and pos).
        edge_attrs: extra edge attributes to copy (beyond edge_attr).
        graph_attrs: extra graph-level attributes to copy (beyond y).
        node_weights_attr: Data attribute to use as node weights.
        edge_weights_attr: Data attribute to use as edge weights.

    Returns:
        DataGraph with ``_coords``, ``_node_weights``, ``_edge_weights``
        populated where applicable.

    Raises:
        ImportError: if ``torch_geometric`` is not installed.
        TypeError: if ``data`` is not a ``torch_geometric.data.Data``
            instance, or if a weight attribute is not a ``torch.Tensor``.
        AttributeError: if a specified weight attribute is missing.
        ValueError: if a weight tensor has an incompatible shape or size.
    """
    try:
        from torch_geometric.data import Data
        from torch_geometric.utils import to_networkx
    except ImportError as e:
        raise ImportError("Please, install the `torch_geometric` package.") from e

    if not isinstance(data, Data):
        raise TypeError("Input must be a torch_geometric.data.Data object.")

    # Validate weight attrs early and keep the squeezed tensors
    node_tensor = (
        cls._validate_weights_attr(data, node_weights_attr, data.num_nodes, "node")
        if node_weights_attr is not None
        else None
    )
    edge_tensor = (
        cls._validate_weights_attr(data, edge_weights_attr, data.num_edges, "edge")
        if edge_weights_attr is not None
        else None
    )

    # Select unique attributes and add default ones only if present in the data
    node_attrs_set = {k for k in {"x", "pos"} if k in data}
    if node_attrs is not None:
        node_attrs_set |= set(node_attrs)
    if node_weights_attr is not None:
        node_attrs_set.add(node_weights_attr)

    edge_attrs_set = {k for k in {"edge_attr"} if k in data}
    if edge_attrs is not None:
        edge_attrs_set |= set(edge_attrs)
    if edge_weights_attr is not None:
        edge_attrs_set.add(edge_weights_attr)

    graph_attrs_set = {k for k in {"y"} if k in data}
    if graph_attrs is not None:
        graph_attrs_set |= set(graph_attrs)

    # Convert to NetworkX (undirected, no self-loops)
    nx_graph = to_networkx(
        data,
        node_attrs=list(node_attrs_set),
        edge_attrs=list(edge_attrs_set),
        graph_attrs=list(graph_attrs_set),
        to_undirected=True,
        remove_self_loops=True,
    )

    # Build the DataGraph: edges carry their data, nodes carry their data
    graph = cls(nx_graph.edges(data=True))
    graph.add_nodes_from(nx_graph.nodes(data=True))
    graph.graph = nx_graph.graph

    # Re-initialize QoolQit internal dicts for all nodes/edges
    graph._coords = {n: None for n in graph.nodes}
    graph._reset_dicts()

    # pos → _coords (stored as list [x, y] by to_networkx)
    for node, node_data in nx_graph.nodes(data=True):
        if "pos" in node_data:
            graph._coords[node] = tuple(node_data["pos"])  # type: ignore[assignment]

    # node_weights_attr → _node_weights
    if node_tensor is not None:
        for i in range(data.num_nodes):
            graph._node_weights[i] = node_tensor[i].item()

    # edge_weights_attr → _edge_weights
    if edge_tensor is not None:
        seen: set = set()
        for idx in range(data.edge_index.shape[1]):
            u = int(data.edge_index[0, idx].item())
            v = int(data.edge_index[1, idx].item())
            key = (min(u, v), max(u, v))
            if key not in seen:
                graph._edge_weights[key] = edge_tensor[idx].item()
                seen.add(key)

    return graph
heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a heavy-hexagonal lattice graph, with respective coordinates.

Parameters:

  • m
    (int) –

    Number of rows of hexagons.

  • n
    (int) –

    Number of columns of hexagons.

  • spacing
    (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Notes

The heavy-hexagonal lattice is a regular hexagonal lattice where each edge is decorated with an additional lattice site.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def heavy_hexagonal(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a heavy-hexagonal lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of hexagons.
        n: Number of columns of hexagons.
        spacing: The distance between adjacent nodes on the final lattice.

    Notes:
        The heavy-hexagonal lattice is a regular hexagonal lattice where
        each edge is decorated with an additional lattice site.
    """
    G_hex = nx.hexagonal_lattice_graph(m, n, with_positions=True)
    pos_unit = nx.get_node_attributes(G_hex, "pos")

    G_heavy = nx.Graph()
    scaling_factor = 2 * spacing

    label_map = {}
    for old_label, (x, y) in pos_unit.items():
        # Relabel to an even-integer grid to make space for midpoint nodes
        new_label = (2 * old_label[0], 2 * old_label[1])
        label_map[old_label] = new_label

        # Scale positions and add the node to the new graph
        new_pos = (x * scaling_factor, y * scaling_factor)
        G_heavy.add_node(new_label, pos=new_pos)

    for u_old, v_old in G_hex.edges():
        u_new, v_new = label_map[u_old], label_map[v_old]

        mid_label = ((u_new[0] + v_new[0]) // 2, (u_new[1] + v_new[1]) // 2)

        pos_u = G_heavy.nodes[u_new]["pos"]
        pos_v = G_heavy.nodes[v_new]["pos"]
        mid_pos = ((pos_u[0] + pos_v[0]) / 2, (pos_u[1] + pos_v[1]) / 2)

        G_heavy.add_node(mid_label, pos=mid_pos)
        G_heavy.add_edge(u_new, mid_label)
        G_heavy.add_edge(mid_label, v_new)

    final_nodes = sorted(list(G_heavy.nodes()))

    final_coords = [G_heavy.nodes[label]["pos"] for label in final_nodes]

    label_to_int = {label: i for i, label in enumerate(final_nodes)}

    final_edges = [(label_to_int[u], label_to_int[v]) for u, v in G_heavy.edges()]

    graph = cls.from_coordinates(final_coords)
    graph.add_edges_from(final_edges)
    graph._reset_dicts()
    return graph
hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a hexagonal lattice graph, with respective coordinates.

Parameters:

  • m
    (int) –

    Number of rows of hexagons.

  • n
    (int) –

    Number of columns of hexagons.

  • spacing
    (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def hexagonal(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a hexagonal lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of hexagons.
        n: Number of columns of hexagons.
        spacing: The distance between adjacent nodes on the final lattice.
    """
    G = nx.hexagonal_lattice_graph(m, n, with_positions=True)
    G = nx.convert_node_labels_to_integers(G)
    pos_unit = nx.get_node_attributes(G, "pos")

    final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}

    graph = cls.from_coordinates(final_pos)
    graph.add_edges_from(G.edges)
    graph._reset_dicts()
    return graph
interactions() -> dict

Rydberg model interaction 1/r^6 between pair of nodes.

Source code in qoolqit/graphs/base_graph.py
def interactions(self) -> dict:
    """Rydberg model interaction 1/r^6 between pair of nodes."""
    return {p: 1.0 / (r**6) for p, r in self.distances().items()}
is_ud_graph() -> bool

Check if the graph is unit-disk.

Source code in qoolqit/graphs/base_graph.py
def is_ud_graph(self) -> bool:
    """Check if the graph is unit-disk."""
    try:
        self.ud_radius_range()
        return True
    except ValueError:
        return False
line(n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a line graph, with the respective coordinates.

Parameters:

  • n
    (int) –

    number of nodes.

  • spacing
    (float, default: 1.0 ) –

    distance between each node.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def line(cls, n: int, spacing: float = 1.0) -> DataGraph:
    """Constructs a line graph, with the respective coordinates.

    Arguments:
        n: number of nodes.
        spacing: distance between each node.
    """
    coords = [(i * spacing, 0.0) for i in range(n)]
    graph = cls.from_coordinates(coords)
    edges = [(i, i + 1) for i in range(0, n - 1)]
    graph.add_edges_from(edges)
    graph._reset_dicts()
    return graph
max_distance(connected: bool | None = None) -> float

Returns the maximum distance in the graph.

Parameters:

  • connected
    (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float:
    """Returns the maximum distance in the graph.

    Arguments:
        connected: if True/False, computes only over connected/disconnected nodes.
    """
    distance: float
    if connected is None:
        distance = max(self.distances(self.all_node_pairs).values())
    elif connected:
        distance = max(self.distances(self.sorted_edges).values())
    else:
        distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values())
    return distance
min_distance(connected: bool | None = None) -> float

Returns the minimum distance in the graph.

Parameters:

  • connected
    (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float:
    """Returns the minimum distance in the graph.

    Arguments:
        connected: if True/False, computes only over connected/disconnected nodes.
    """
    distance: float
    if connected is None:
        distance = min(self.distances(self.all_node_pairs).values())
    elif connected:
        distance = min(self.distances(self.sorted_edges).values())
    else:
        distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values())
    return distance
random_er(n: int, p: float, seed: int | None = None) -> DataGraph classmethod

Constructs an Erdős–Rényi random graph.

Parameters:

  • n
    (int) –

    number of nodes.

  • p
    (float) –

    probability that any two nodes connect.

  • seed
    (int | None, default: None ) –

    random seed.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def random_er(cls, n: int, p: float, seed: int | None = None) -> DataGraph:
    """Constructs an Erdős–Rényi random graph.

    Arguments:
        n: number of nodes.
        p: probability that any two nodes connect.
        seed: random seed.
    """
    base_graph = nx.erdos_renyi_graph(n, p, seed)
    graph = DataGraph.from_nodes(list(base_graph.nodes))
    graph.add_edges_from(base_graph.edges)
    graph._reset_dicts()
    return graph
random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph classmethod

Constructs a random unit-disk graph.

The nodes are sampled uniformly from a square of size (L x L). If L is not given, it is estimated based on a rough heuristic that of packing N nodes on a square of side L such that the expected minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).

Parameters:

  • n
    (int) –

    number of nodes.

  • radius
    (float, default: 1.0 ) –

    radius to use for defining the unit-disk edges.

  • L
    (float | None, default: None ) –

    size of the square on which to sample the node coordinates.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def random_ud(
    cls,
    n: int,
    radius: float = 1.0,
    L: float | None = None,
) -> DataGraph:
    """Constructs a random unit-disk graph.

    The nodes are sampled uniformly from a square of size (L x L).
    If L is not given, it is estimated based on a rough heuristic that
    of packing N nodes on a square of side L such that the expected
    minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).

    Arguments:
        n: number of nodes.
        radius: radius to use for defining the unit-disk edges.
        L: size of the square on which to sample the node coordinates.
    """
    if L is None:
        L = (radius / 2) * ((np.pi * n) ** 0.5)
    coords = random_coords(n, L)
    graph = cls.from_coordinates(coords)
    edges = graph.ud_edges(radius)
    graph.add_edges_from(edges)
    graph._reset_dicts()
    return graph
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

  • scaling
    (float | None, default: None ) –

    value to scale by.

  • spacing
    (float | None, default: None ) –

    value to set as the minimum distance in the graph.

Source code in qoolqit/graphs/base_graph.py
def rescale_coords(
    self,
    *args: Any,
    scaling: float | None = None,
    spacing: float | None = None,
) -> None:
    """Rescales the node coordinates by a factor.

    Accepts either a scaling or a spacing factor.

    Arguments:
        scaling: value to scale by.
        spacing: value to set as the minimum distance in the graph.
    """
    if self.has_coords:
        msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument."
        if (len(args) > 0) or (scaling is None and spacing is None):
            raise TypeError(msg)
        if scaling is None and spacing is not None:
            self._coords = space_coords(self._coords, spacing)
        elif spacing is None and scaling is not None:
            self._coords = scale_coords(self._coords, scaling)
        else:
            raise TypeError(msg)
    else:
        raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
set_ud_edges(radius: float) -> None

Reset the set of edges to be equal to the set of unit-disk edges.

Source code in qoolqit/graphs/data_graph.py
def set_ud_edges(self, radius: float) -> None:
    """Reset the set of edges to be equal to the set of unit-disk edges."""
    super().set_ud_edges(radius=radius)
    self._edge_weights = {e: None for e in self.sorted_edges}
square(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a square lattice graph, with respective coordinates.

Parameters:

  • m
    (int) –

    Number of rows of square.

  • n
    (int) –

    Number of columns of square.

  • spacing
    (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def square(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a square lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of square.
        n: Number of columns of square.
        spacing: The distance between adjacent nodes on the final lattice.
    """
    G = nx.grid_2d_graph(m, n)
    final_coords = [(x * spacing, y * spacing) for (x, y) in list(G.nodes)]
    G = nx.convert_node_labels_to_integers(G)

    graph = DataGraph.from_coordinates(final_coords)
    graph.add_edges_from(G.edges)
    graph._reset_dicts()
    return graph
to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data

Convert the DataGraph to a PyTorch Geometric Data object.

Requires torch_geometric. Uses from_networkx internally.

Default attributes exported (if present on the graph):

  • Node "x"data.x; Edge "edge_attr"data.edge_attr
  • Graph "y"data.y

Use node_attrs, edge_attrs, graph_attrs for extras.

QoolQit internal dicts exported when populated:

  • _coordsdata.pos (float64, shape (N, 2))
  • _node_weightsdata.<node_weights_attr> (float64, shape (N,)). Defaults to "weight".
  • _edge_weightsdata.<edge_weights_attr> (float64, shape (2*E,)). Defaults to "edge_weight".

Parameters:

  • node_attrs
    (Iterable[str] | None, default: None ) –

    extra node attributes to export (beyond x).

  • edge_attrs
    (Iterable[str] | None, default: None ) –

    extra edge attributes to export (beyond edge_attr).

  • graph_attrs
    (Iterable[str] | None, default: None ) –

    extra graph-level attributes to export (beyond y).

  • node_weights_attr
    (str, default: 'weight' ) –

    Data attribute name for node weights. Defaults to "weight".

  • edge_weights_attr
    (str, default: 'edge_weight' ) –

    Data attribute name for edge weights. Defaults to "edge_weight".

Returns:

  • Data

    PyTorch Geometric Data object.

Raises:

  • ImportError

    if torch_geometric is not installed.

Source code in qoolqit/graphs/data_graph.py
def to_pyg(
    self,
    node_attrs: Iterable[str] | None = None,
    edge_attrs: Iterable[str] | None = None,
    graph_attrs: Iterable[str] | None = None,
    node_weights_attr: str = "weight",
    edge_weights_attr: str = "edge_weight",
) -> torch_geometric.data.Data:
    """Convert the DataGraph to a PyTorch Geometric Data object.

    Requires ``torch_geometric``. Uses ``from_networkx`` internally.

    **Default attributes exported (if present on the graph):**

    - Node ``"x"`` → ``data.x``; Edge ``"edge_attr"`` → ``data.edge_attr``
    - Graph ``"y"`` → ``data.y``

    Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.

    **QoolQit internal dicts exported when populated:**

    - ``_coords`` → ``data.pos`` (float64, shape ``(N, 2)``)
    - ``_node_weights`` → ``data.<node_weights_attr>`` (float64, shape
      ``(N,)``). Defaults to ``"weight"``.
    - ``_edge_weights`` → ``data.<edge_weights_attr>`` (float64, shape
      ``(2*E,)``). Defaults to ``"edge_weight"``.

    Arguments:
        node_attrs: extra node attributes to export (beyond x).
        edge_attrs: extra edge attributes to export (beyond edge_attr).
        graph_attrs: extra graph-level attributes to export (beyond y).
        node_weights_attr: Data attribute name for node weights.
            Defaults to ``"weight"``.
        edge_weights_attr: Data attribute name for edge weights.
            Defaults to ``"edge_weight"``.

    Returns:
        PyTorch Geometric Data object.

    Raises:
        ImportError: if ``torch_geometric`` is not installed.
    """
    try:
        import torch
        from torch_geometric.utils import from_networkx
    except ImportError as e:
        raise ImportError("Please, install the `torch_geometric` package.") from e

    node_attrs_set = set(node_attrs) if node_attrs else set()
    edge_attrs_set = set(edge_attrs) if edge_attrs else set()
    graph_attrs_list = list(graph_attrs) if graph_attrs else []

    # Add default PyG attributes if present in the graph
    if any("x" in d for _, d in self.nodes(data=True)):
        node_attrs_set.add("x")
    if any("edge_attr" in d for _, _, d in self.edges(data=True)):
        edge_attrs_set.add("edge_attr")
    if "y" in self.graph:
        graph_attrs_list.append("y")

    # Build a filtered copy with only the requested attributes
    filtered_graph = nx.Graph()
    filtered_graph.add_nodes_from(self.nodes())
    filtered_graph.add_edges_from(self.edges())

    for node, node_data in self.nodes(data=True):
        for key, value in node_data.items():
            if key in node_attrs_set:
                filtered_graph.nodes[node][key] = value
    for u, v, edge_data in self.edges(data=True):
        for key, value in edge_data.items():
            if key in edge_attrs_set:
                filtered_graph.edges[u, v][key] = value
    for attr in graph_attrs_list:
        if attr in self.graph:
            filtered_graph.graph[attr] = self.graph[attr]

    data = from_networkx(filtered_graph)

    # Export _coords → pos
    if self.has_coords:
        positions = [self._coords[n] for n in sorted(self.nodes())]
        data.pos = torch.tensor(positions, dtype=torch.float64)

    # Export _node_weights → node_weights_attr
    if self.has_node_weights:
        weights = [self._node_weights[n] for n in sorted(self.nodes())]
        setattr(data, node_weights_attr, torch.tensor(weights, dtype=torch.float64))

    # Export _edge_weights → edge_weights_attr (one value per directed edge in edge_index)
    if self.has_edge_weights:
        edge_weights: list[float] = []
        for i in range(data.edge_index.shape[1]):
            u, v = int(data.edge_index[0, i].item()), int(data.edge_index[1, i].item())
            edge_key = (min(u, v), max(u, v))
            edge_weights.append(float(self._edge_weights[edge_key]))  # type: ignore[arg-type]
        setattr(data, edge_weights_attr, torch.tensor(edge_weights, dtype=torch.float64))

    return data
triangular(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a triangular lattice graph, with respective coordinates.

Parameters:

  • m
    (int) –

    Number of rows of triangles.

  • n
    (int) –

    Number of columns of triangles.

  • spacing
    (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def triangular(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a triangular lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of triangles.
        n: Number of columns of triangles.
        spacing: The distance between adjacent nodes on the final lattice.
    """
    G = nx.triangular_lattice_graph(m, n, with_positions=True)
    G = nx.convert_node_labels_to_integers(G)
    pos_unit = nx.get_node_attributes(G, "pos")

    final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}

    graph = cls.from_coordinates(final_pos)
    graph.add_edges_from(G.edges)
    graph._reset_dicts()
    return graph
ud_edges(radius: float) -> set

Returns the set of edges given by the intersection of circles of a given radius.

Parameters:

  • radius
    (float) –

    the value

Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set:
    """Returns the set of edges given by the intersection of circles of a given radius.

    Arguments:
        radius: the value
    """
    if self.has_coords:
        return set(e for e, d in self.distances().items() if less_or_equal(d, radius))
    else:
        raise AttributeError("Getting unit disk edges is not valid without coordinates.")
ud_radius_range() -> tuple

Return the range (R_min, R_max) where the graph is unit-disk.

The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:

graph.ud_edges(radius = R) == graph.sorted edges

Source code in qoolqit/graphs/base_graph.py
def ud_radius_range(self) -> tuple:
    """Return the range (R_min, R_max) where the graph is unit-disk.

    The graph is unit-disk if the maximum distance between all connected nodes is
    smaller than the minimum distance between disconnected nodes. This means that
    for any value R in that interval, the following condition is true:

    graph.ud_edges(radius = R) == graph.sorted edges
    """
    if self.has_coords:
        n_edges = len(self.sorted_edges)
        if n_edges == 0:
            # If the graph is empty and has coordinates
            return (0.0, self.min_distance(connected=False))
        elif n_edges == len(self.all_node_pairs):
            # If the graph is fully connected
            return (self.max_distance(connected=True), float("inf"))
        elif self.max_distance(connected=True) < self.min_distance(connected=False):
            return (self.max_distance(connected=True), self.min_distance(connected=False))
        else:
            raise ValueError("Graph is not unit disk.")
    else:
        raise AttributeError("Checking if graph is unit disk is not valid without coordinates.")

all_node_pairs(nodes: Iterable) -> set

Return all pairs of nodes (u, v) where u < v.

Parameters:

  • nodes
    (Iterable) –

    set of node indices.

Source code in qoolqit/graphs/utils.py
def all_node_pairs(nodes: Iterable) -> set:
    """Return all pairs of nodes (u, v) where u < v.

    Arguments:
        nodes: set of node indices.
    """
    return set(filter(lambda x: x[0] < x[1], product(nodes, nodes)))

distances(coords: dict, edge_list: Iterable) -> dict

Return a dictionary of edge distances.

Parameters:

  • coords
    (dict) –

    dictionary of node coordinates.

  • edge_list
    (Iterable) –

    edge list to compute the distances for.

Source code in qoolqit/graphs/utils.py
def distances(coords: dict, edge_list: Iterable) -> dict:
    """Return a dictionary of edge distances.

    Arguments:
        coords: dictionary of node coordinates.
        edge_list: edge list to compute the distances for.
    """
    return {edge: dist(coords[edge[0]], coords[edge[1]]) for edge in edge_list}

random_coords(n: int, L: float = 1.0) -> list

Generate a random set of node coordinates on a square of side L.

Parameters:

  • n
    (int) –

    number of coordinate pairs to generate.

  • L
    (float, default: 1.0 ) –

    side of the square.

Source code in qoolqit/graphs/utils.py
def random_coords(n: int, L: float = 1.0) -> list:
    """Generate a random set of node coordinates on a square of side L.

    Arguments:
        n: number of coordinate pairs to generate.
        L: side of the square.
    """
    x_coords = np.random.uniform(low=-L / 2, high=L / 2, size=(n,)).tolist()
    y_coords = np.random.uniform(low=-L / 2, high=L / 2, size=(n,)).tolist()
    return [(x, y) for x, y in zip(x_coords, y_coords)]

random_edge_list(nodes: Iterable, k: int) -> list

Generates a random set of k edges linkings items from a set of nodes.

Source code in qoolqit/graphs/utils.py
def random_edge_list(nodes: Iterable, k: int) -> list:
    """Generates a random set of k edges linkings items from a set of nodes."""
    all_edges = all_node_pairs(nodes)
    return random.sample(tuple(all_edges), k=k)

scale_coords(coords: dict, scaling: float) -> dict

Scale the coordinates by a given value.

Parameters:

  • coords
    (dict) –

    dictionary of node coordinates.

  • scaling
    (float) –

    value to scale by.

Source code in qoolqit/graphs/utils.py
def scale_coords(coords: dict, scaling: float) -> dict:
    """Scale the coordinates by a given value.

    Arguments:
        coords: dictionary of node coordinates.
        scaling: value to scale by.
    """
    scaled_coords = {i: (c[0] * scaling, c[1] * scaling) for i, c in coords.items()}
    return scaled_coords

space_coords(coords: dict, spacing: float) -> dict

Spaces the coordinates so the minimum distance is equal to a set spacing.

Parameters:

  • coords
    (dict) –

    dictionary of node coordinates.

  • spacing
    (float) –

    value to set as minimum distance.

Source code in qoolqit/graphs/utils.py
def space_coords(coords: dict, spacing: float) -> dict:
    """Spaces the coordinates so the minimum distance is equal to a set spacing.

    Arguments:
        coords: dictionary of node coordinates.
        spacing: value to set as minimum distance.
    """
    pairs = all_node_pairs(list(coords.keys()))
    min_dist = min(distances(coords, pairs).values())
    scale_factor = spacing / min_dist
    return scale_coords(coords, scale_factor)

base_graph

Classes:

  • BaseGraph

    The BaseGraph in QoolQit, directly inheriting from the NetworkX Graph.

BaseGraph(edges: Iterable = [])

The BaseGraph in QoolQit, directly inheriting from the NetworkX Graph.

Defines basic functionalities for graphs within the Rydberg Analog, such as instantiating from a set of node coordinates, directly accessing node distances, and checking if the graph is unit-disk.

Parameters:

  • edges
    (Iterable, default: [] ) –

    set of edge tuples (i, j)

Methods:

  • distances

    Returns a dictionary of distances for a given set of edges.

  • draw

    Draw the graph.

  • from_coordinates

    Construct a base graph from a set of coordinates.

  • from_nodes

    Construct a base graph from a set of nodes.

  • from_nx

    Convert a NetworkX Graph object into a QoolQit graph instance.

  • interactions

    Rydberg model interaction 1/r^6 between pair of nodes.

  • is_ud_graph

    Check if the graph is unit-disk.

  • max_distance

    Returns the maximum distance in the graph.

  • min_distance

    Returns the minimum distance in the graph.

  • rescale_coords

    Rescales the node coordinates by a factor.

  • set_ud_edges

    Reset the set of edges to be equal to the set of unit-disk edges.

  • ud_edges

    Returns the set of edges given by the intersection of circles of a given radius.

  • ud_radius_range

    Return the range (R_min, R_max) where the graph is unit-disk.

Attributes:

  • all_node_pairs (set) –

    Return a list of all possible node pairs in the graph.

  • coords (dict) –

    Return the dictionary of node coordinates.

  • has_coords (bool) –

    Check if the graph has coordinates.

  • has_edge_weights (bool) –

    Check if the graph has edge weights.

  • has_edges (bool) –

    Check if the graph has edges.

  • has_node_weights (bool) –

    Check if the graph has node weights.

  • sorted_edges (set) –

    Returns the set of edges (u, v) such that (u < v).

Source code in qoolqit/graphs/base_graph.py
def __init__(self, edges: Iterable = []) -> None:
    """
    Default constructor for the BaseGraph.

    Arguments:
        edges: set of edge tuples (i, j)
    """
    if edges and not isinstance(edges, Iterable):
        raise TypeError("Input is not a valid edge list.")

    super().__init__()
    self.add_edges_from(edges)
    self._coords = {i: None for i in self.nodes}
    self._reset_dicts()
all_node_pairs: set property

Return a list of all possible node pairs in the graph.

coords: dict property writable

Return the dictionary of node coordinates.

has_coords: bool property

Check if the graph has coordinates.

Requires all nodes to have coordinates.

has_edge_weights: bool property

Check if the graph has edge weights.

Requires all edges to have a weight.

has_edges: bool property

Check if the graph has edges.

has_node_weights: bool property

Check if the graph has node weights.

Requires all nodes to have a weight.

sorted_edges: set property

Returns the set of edges (u, v) such that (u < v).

distances(edge_list: Iterable | None = None) -> dict

Returns a dictionary of distances for a given set of edges.

Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.

Parameters:

  • edge_list (Iterable | None, default: None ) –

    set of edges.

Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict:
    """Returns a dictionary of distances for a given set of edges.

    Distances are calculated directly from the coordinates. Raises an error
    if there are no coordinates on the graph.

    Arguments:
        edge_list: set of edges.
    """
    if self.has_coords:
        if edge_list is None:
            edge_list = self.all_node_pairs
        elif len(edge_list) == 0:  # type: ignore [arg-type]
            raise ValueError("Trying to compute distances for an empty edge list.")
        return distances(self.coords, edge_list)
    else:
        raise AttributeError("Trying to compute distances for a graph without coordinates.")
draw(ax: Axes | None = None, **kwargs: Any) -> None

Draw the graph.

Uses the draw_networkx function from NetworkX.

Parameters:

  • ax (Axes | None, default: None ) –

    Axes object to draw on. If None, uses the current Axes.

  • **kwargs (Any, default: {} ) –

    keyword-arguments to pass to draw_networkx.

Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None:
    """Draw the graph.

    Uses the draw_networkx function from NetworkX.

    Args:
        ax: Axes object to draw on. If None, uses the current Axes.
        **kwargs: keyword-arguments to pass to draw_networkx.
    """
    if self.has_coords:
        if "hide_ticks" not in kwargs:
            kwargs["hide_ticks"] = False

        nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)

        if ax is None:
            ax = plt.gca()
        ax.set_xlabel("x")
        ax.set_ylabel("y")
        ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)

        # minimum ybox
        ylim = ax.get_ylim()
        if (ylim[1] - ylim[0]) < 2:
            y_center = (ylim[0] + ylim[1]) / 2
            ax.set_ylim(y_center - 1, y_center + 1)
        plt.tight_layout()
    else:
        nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph classmethod

Construct a base graph from a set of coordinates.

Parameters:

  • coords (list | dict) –

    list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_coordinates(cls, coords: list | dict) -> BaseGraph:
    """Construct a base graph from a set of coordinates.

    Arguments:
        coords: list or dictionary of coordinate pairs.
    """
    if isinstance(coords, list):
        nodes = list(range(len(coords)))
        coords_dict = {i: pos for i, pos in enumerate(coords)}
    elif isinstance(coords, dict):
        nodes = list(coords.keys())
        coords_dict = coords
    graph = cls.from_nodes(nodes)
    graph._coords = coords_dict
    graph._reset_dicts()
    return graph
from_nodes(nodes: Iterable) -> BaseGraph classmethod

Construct a base graph from a set of nodes.

Parameters:

  • nodes (Iterable) –

    set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nodes(cls, nodes: Iterable) -> BaseGraph:
    """Construct a base graph from a set of nodes.

    Arguments:
        nodes: set of nodes.
    """
    graph = cls()
    graph.add_nodes_from(nodes)
    graph._coords = {i: None for i in graph.nodes}
    graph._reset_dicts()
    return graph
from_nx(g: nx.Graph) -> BaseGraph classmethod

Convert a NetworkX Graph object into a QoolQit graph instance.

The input networkx.Graph graph must be defined only with the following allowed

Node attributes

pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers. weight: represents the node weight. Must be a real number.

Edge attributes: weight: represents the edge weight. Must be a real number.

Returns an instance of the class with following attributes
  • _node_weights : dict[node, float or None]
  • _edge_weights : dict[(u,v), float or None]
  • _coords : dict[node, (float,float) or None]
Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nx(cls, g: nx.Graph) -> BaseGraph:
    """Convert a NetworkX Graph object into a QoolQit graph instance.

    The input `networkx.Graph` graph must be defined only with the following allowed

    Node attributes:
        pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers.
        weight: represents the node weight. Must be a real number.
    Edge attributes:
        weight: represents the edge weight. Must be a real number.

    Returns an instance of the class with following attributes:
        - _node_weights : dict[node, float or None]
        - _edge_weights : dict[(u,v), float or None]
        - _coords       : dict[node, (float,float) or None]
    """
    if not isinstance(g, nx.Graph):
        raise TypeError("Input must be a networkx.Graph instance.")

    g = nx.convert_node_labels_to_integers(g)
    num_nodes = len(g.nodes)
    num_edges = len(g.edges)

    # validate node attributes
    for name, data in g.nodes.data():
        unexpected_keys = set(data) - {"weight", "pos"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in node attributes.")

    node_pos = nx.get_node_attributes(g, "pos")
    if node_pos:
        if len(node_pos) != num_nodes:
            raise ValueError("Node attribute `pos` must be defined for all nodes")
        for name, pos in node_pos.items():
            is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2)
            is_real = all(isinstance(p, (float, int)) for p in pos)
            if not (is_2D & is_real):
                raise TypeError(
                    f"In node {name} the `pos` attribute must be a 2D tuple/list"
                    f" of real numbers, got {pos} instead."
                )
    node_weights = nx.get_node_attributes(g, "weight")
    if node_weights:
        if len(node_weights) != num_nodes:
            raise ValueError("Node attribute `weight` must be defined for all nodes")
        for name, weight in node_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In node {name} the `weight` attribute must be a real number, "
                    f"got {type(weight)} instead."
                    ""
                )

    # validate edge attributes
    for u, v, data in g.edges.data():
        unexpected_keys = set(data) - {"weight"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in edge attributes.")
    edge_weights = nx.get_edge_attributes(g, "weight")
    if edge_weights:
        if len(edge_weights) != num_edges:
            raise ValueError("Edge attribute `weight` must be defined for all edges")
        for name, weight in edge_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In edge {name}, the attribute `weight` must be a real number, "
                    f"got {type(weight)} instead."
                )

    # build the instance of the graph
    graph = cls()
    graph.add_nodes_from(g.nodes)
    graph.add_edges_from(g.edges)
    graph._node_weights = nx.get_node_attributes(g, "weight", default=None)
    graph._coords = nx.get_node_attributes(g, "pos", default=None)
    graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)

    return graph
interactions() -> dict

Rydberg model interaction 1/r^6 between pair of nodes.

Source code in qoolqit/graphs/base_graph.py
def interactions(self) -> dict:
    """Rydberg model interaction 1/r^6 between pair of nodes."""
    return {p: 1.0 / (r**6) for p, r in self.distances().items()}
is_ud_graph() -> bool

Check if the graph is unit-disk.

Source code in qoolqit/graphs/base_graph.py
def is_ud_graph(self) -> bool:
    """Check if the graph is unit-disk."""
    try:
        self.ud_radius_range()
        return True
    except ValueError:
        return False
max_distance(connected: bool | None = None) -> float

Returns the maximum distance in the graph.

Parameters:

  • connected (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float:
    """Returns the maximum distance in the graph.

    Arguments:
        connected: if True/False, computes only over connected/disconnected nodes.
    """
    distance: float
    if connected is None:
        distance = max(self.distances(self.all_node_pairs).values())
    elif connected:
        distance = max(self.distances(self.sorted_edges).values())
    else:
        distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values())
    return distance
min_distance(connected: bool | None = None) -> float

Returns the minimum distance in the graph.

Parameters:

  • connected (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float:
    """Returns the minimum distance in the graph.

    Arguments:
        connected: if True/False, computes only over connected/disconnected nodes.
    """
    distance: float
    if connected is None:
        distance = min(self.distances(self.all_node_pairs).values())
    elif connected:
        distance = min(self.distances(self.sorted_edges).values())
    else:
        distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values())
    return distance
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

  • scaling (float | None, default: None ) –

    value to scale by.

  • spacing (float | None, default: None ) –

    value to set as the minimum distance in the graph.

Source code in qoolqit/graphs/base_graph.py
def rescale_coords(
    self,
    *args: Any,
    scaling: float | None = None,
    spacing: float | None = None,
) -> None:
    """Rescales the node coordinates by a factor.

    Accepts either a scaling or a spacing factor.

    Arguments:
        scaling: value to scale by.
        spacing: value to set as the minimum distance in the graph.
    """
    if self.has_coords:
        msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument."
        if (len(args) > 0) or (scaling is None and spacing is None):
            raise TypeError(msg)
        if scaling is None and spacing is not None:
            self._coords = space_coords(self._coords, spacing)
        elif spacing is None and scaling is not None:
            self._coords = scale_coords(self._coords, scaling)
        else:
            raise TypeError(msg)
    else:
        raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
set_ud_edges(radius: float) -> None

Reset the set of edges to be equal to the set of unit-disk edges.

Parameters:

  • radius (float) –

    the radius to use in determining the set of unit-disk edges.

Source code in qoolqit/graphs/base_graph.py
def set_ud_edges(self, radius: float) -> None:
    """Reset the set of edges to be equal to the set of unit-disk edges.

    Arguments:
        radius: the radius to use in determining the set of unit-disk edges.
    """
    self.remove_edges_from(list(self.edges))
    self.add_edges_from(self.ud_edges(radius))
ud_edges(radius: float) -> set

Returns the set of edges given by the intersection of circles of a given radius.

Parameters:

  • radius (float) –

    the value

Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set:
    """Returns the set of edges given by the intersection of circles of a given radius.

    Arguments:
        radius: the value
    """
    if self.has_coords:
        return set(e for e, d in self.distances().items() if less_or_equal(d, radius))
    else:
        raise AttributeError("Getting unit disk edges is not valid without coordinates.")
ud_radius_range() -> tuple

Return the range (R_min, R_max) where the graph is unit-disk.

The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:

graph.ud_edges(radius = R) == graph.sorted edges

Source code in qoolqit/graphs/base_graph.py
def ud_radius_range(self) -> tuple:
    """Return the range (R_min, R_max) where the graph is unit-disk.

    The graph is unit-disk if the maximum distance between all connected nodes is
    smaller than the minimum distance between disconnected nodes. This means that
    for any value R in that interval, the following condition is true:

    graph.ud_edges(radius = R) == graph.sorted edges
    """
    if self.has_coords:
        n_edges = len(self.sorted_edges)
        if n_edges == 0:
            # If the graph is empty and has coordinates
            return (0.0, self.min_distance(connected=False))
        elif n_edges == len(self.all_node_pairs):
            # If the graph is fully connected
            return (self.max_distance(connected=True), float("inf"))
        elif self.max_distance(connected=True) < self.min_distance(connected=False):
            return (self.max_distance(connected=True), self.min_distance(connected=False))
        else:
            raise ValueError("Graph is not unit disk.")
    else:
        raise AttributeError("Checking if graph is unit disk is not valid without coordinates.")

data_graph

Classes:

  • DataGraph

    The main graph structure to represent problem data.

DataGraph(edges: Iterable = [])

The main graph structure to represent problem data.

Parameters:

  • edges
    (Iterable, default: [] ) –

    set of edge tuples (i, j)

Methods:

  • circle

    Constructs a circle graph, with the respective coordinates.

  • distances

    Returns a dictionary of distances for a given set of edges.

  • draw

    Draw the graph.

  • from_coordinates

    Construct a base graph from a set of coordinates.

  • from_matrix

    Constructs a graph from a symmetric square matrix.

  • from_nodes

    Construct a base graph from a set of nodes.

  • from_nx

    Convert a NetworkX Graph object into a QoolQit graph instance.

  • from_pyg

    Convert a PyTorch Geometric Data object into a DataGraph instance.

  • heavy_hexagonal

    Constructs a heavy-hexagonal lattice graph, with respective coordinates.

  • hexagonal

    Constructs a hexagonal lattice graph, with respective coordinates.

  • interactions

    Rydberg model interaction 1/r^6 between pair of nodes.

  • is_ud_graph

    Check if the graph is unit-disk.

  • line

    Constructs a line graph, with the respective coordinates.

  • max_distance

    Returns the maximum distance in the graph.

  • min_distance

    Returns the minimum distance in the graph.

  • random_er

    Constructs an Erdős–Rényi random graph.

  • random_ud

    Constructs a random unit-disk graph.

  • rescale_coords

    Rescales the node coordinates by a factor.

  • set_ud_edges

    Reset the set of edges to be equal to the set of unit-disk edges.

  • square

    Constructs a square lattice graph, with respective coordinates.

  • to_pyg

    Convert the DataGraph to a PyTorch Geometric Data object.

  • triangular

    Constructs a triangular lattice graph, with respective coordinates.

  • ud_edges

    Returns the set of edges given by the intersection of circles of a given radius.

  • ud_radius_range

    Return the range (R_min, R_max) where the graph is unit-disk.

Attributes:

  • all_node_pairs (set) –

    Return a list of all possible node pairs in the graph.

  • coords (dict) –

    Return the dictionary of node coordinates.

  • edge_weights (dict) –

    Return the dictionary of edge weights.

  • has_coords (bool) –

    Check if the graph has coordinates.

  • has_edge_weights (bool) –

    Check if the graph has edge weights.

  • has_edges (bool) –

    Check if the graph has edges.

  • has_node_weights (bool) –

    Check if the graph has node weights.

  • node_weights (dict) –

    Return the dictionary of node weights.

  • sorted_edges (set) –

    Returns the set of edges (u, v) such that (u < v).

Source code in qoolqit/graphs/data_graph.py
def __init__(self, edges: Iterable = []) -> None:
    """
    Default constructor for the BaseGraph.

    Arguments:
        edges: set of edge tuples (i, j)
    """
    super().__init__(edges)
all_node_pairs: set property

Return a list of all possible node pairs in the graph.

coords: dict property writable

Return the dictionary of node coordinates.

edge_weights: dict property writable

Return the dictionary of edge weights.

has_coords: bool property

Check if the graph has coordinates.

Requires all nodes to have coordinates.

has_edge_weights: bool property

Check if the graph has edge weights.

Requires all edges to have a weight.

has_edges: bool property

Check if the graph has edges.

has_node_weights: bool property

Check if the graph has node weights.

Requires all nodes to have a weight.

node_weights: dict property writable

Return the dictionary of node weights.

sorted_edges: set property

Returns the set of edges (u, v) such that (u < v).

circle(n: int, spacing: float = 1.0, center: tuple = (0.0, 0.0)) -> DataGraph classmethod

Constructs a circle graph, with the respective coordinates.

Parameters:

  • n (int) –

    number of nodes.

  • spacing (float, default: 1.0 ) –

    distance between each node.

  • center (tuple, default: (0.0, 0.0) ) –

    point (x, y) to set as the center of the graph.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def circle(
    cls,
    n: int,
    spacing: float = 1.0,
    center: tuple = (0.0, 0.0),
) -> DataGraph:
    """Constructs a circle graph, with the respective coordinates.

    Arguments:
        n: number of nodes.
        spacing: distance between each node.
        center: point (x, y) to set as the center of the graph.
    """

    d_theta = (2.0 * np.pi) / n
    r = spacing / (2.0 * np.sin(np.pi / n))
    theta = np.linspace(0.0, 2.0 * np.pi - d_theta, n)
    coords = [
        (x + center[0], y + center[1]) for x, y in zip(r * np.cos(theta), r * np.sin(theta))
    ]
    edges = [(i, i + 1) for i in range(n - 1)] + [(n - 1, 0)]
    graph = cls.from_coordinates(coords)
    graph.add_edges_from(edges)
    graph._reset_dicts()
    return graph
distances(edge_list: Iterable | None = None) -> dict

Returns a dictionary of distances for a given set of edges.

Distances are calculated directly from the coordinates. Raises an error if there are no coordinates on the graph.

Parameters:

  • edge_list (Iterable | None, default: None ) –

    set of edges.

Source code in qoolqit/graphs/base_graph.py
def distances(self, edge_list: Iterable | None = None) -> dict:
    """Returns a dictionary of distances for a given set of edges.

    Distances are calculated directly from the coordinates. Raises an error
    if there are no coordinates on the graph.

    Arguments:
        edge_list: set of edges.
    """
    if self.has_coords:
        if edge_list is None:
            edge_list = self.all_node_pairs
        elif len(edge_list) == 0:  # type: ignore [arg-type]
            raise ValueError("Trying to compute distances for an empty edge list.")
        return distances(self.coords, edge_list)
    else:
        raise AttributeError("Trying to compute distances for a graph without coordinates.")
draw(ax: Axes | None = None, **kwargs: Any) -> None

Draw the graph.

Uses the draw_networkx function from NetworkX.

Parameters:

  • ax (Axes | None, default: None ) –

    Axes object to draw on. If None, uses the current Axes.

  • **kwargs (Any, default: {} ) –

    keyword-arguments to pass to draw_networkx.

Source code in qoolqit/graphs/base_graph.py
def draw(self, ax: Axes | None = None, **kwargs: Any) -> None:
    """Draw the graph.

    Uses the draw_networkx function from NetworkX.

    Args:
        ax: Axes object to draw on. If None, uses the current Axes.
        **kwargs: keyword-arguments to pass to draw_networkx.
    """
    if self.has_coords:
        if "hide_ticks" not in kwargs:
            kwargs["hide_ticks"] = False

        nx.draw_networkx(self, pos=self.coords, ax=ax, **kwargs)

        if ax is None:
            ax = plt.gca()
        ax.set_xlabel("x")
        ax.set_ylabel("y")
        ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)

        # minimum ybox
        ylim = ax.get_ylim()
        if (ylim[1] - ylim[0]) < 2:
            y_center = (ylim[0] + ylim[1]) / 2
            ax.set_ylim(y_center - 1, y_center + 1)
        plt.tight_layout()
    else:
        nx.draw_networkx(self, ax=ax, **kwargs)
from_coordinates(coords: list | dict) -> BaseGraph classmethod

Construct a base graph from a set of coordinates.

Parameters:

  • coords (list | dict) –

    list or dictionary of coordinate pairs.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_coordinates(cls, coords: list | dict) -> BaseGraph:
    """Construct a base graph from a set of coordinates.

    Arguments:
        coords: list or dictionary of coordinate pairs.
    """
    if isinstance(coords, list):
        nodes = list(range(len(coords)))
        coords_dict = {i: pos for i, pos in enumerate(coords)}
    elif isinstance(coords, dict):
        nodes = list(coords.keys())
        coords_dict = coords
    graph = cls.from_nodes(nodes)
    graph._coords = coords_dict
    graph._reset_dicts()
    return graph
from_matrix(data: ArrayLike) -> DataGraph classmethod

Constructs a graph from a symmetric square matrix.

The diagonal values are set as the node weights. For each entry (i, j) where M[i, j] != 0 an edge (i, j) is added to the graph and the value M[i, j] is set as its weight.

Parameters:

  • data (ArrayLike) –

    symmetric square matrix.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def from_matrix(cls, data: ArrayLike) -> DataGraph:
    """Constructs a graph from a symmetric square matrix.

    The diagonal values are set as the node weights. For each entry (i, j)
    where M[i, j] != 0 an edge (i, j) is added to the graph and the value
    M[i, j] is set as its weight.

    Arguments:
        data: symmetric square matrix.
    """
    if data.ndim != 2:
        raise ValueError("2D Matrix required.")
    if not np.allclose(data, data.T, rtol=0.0, atol=ATOL_32):
        raise ValueError("Matrix must be symmetric.")

    diag = np.diag(data)
    n_nodes = len(diag)
    node_weights = {i: diag[i] for i in range(n_nodes)}
    if np.allclose(diag, np.zeros(n_nodes), rtol=0.0, atol=ATOL_32):
        node_weights = {i: None for i in range(n_nodes)}
    else:
        node_weights = {i: diag[i].item() for i in range(n_nodes)}

    data[data <= ATOL_32] = 0.0
    non_zero = data.nonzero()
    i_list = non_zero[0].tolist()
    j_list = non_zero[1].tolist()

    edge_list = [(i, j) for i, j in zip(i_list, j_list) if i < j]
    edge_weights = {(i, j): data[i, j].item() for i, j in edge_list}

    graph = cls.from_nodes(range(n_nodes))
    graph.add_edges_from(edge_list)
    graph.node_weights = node_weights
    graph.edge_weights = edge_weights
    return graph
from_nodes(nodes: Iterable) -> BaseGraph classmethod

Construct a base graph from a set of nodes.

Parameters:

  • nodes (Iterable) –

    set of nodes.

Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nodes(cls, nodes: Iterable) -> BaseGraph:
    """Construct a base graph from a set of nodes.

    Arguments:
        nodes: set of nodes.
    """
    graph = cls()
    graph.add_nodes_from(nodes)
    graph._coords = {i: None for i in graph.nodes}
    graph._reset_dicts()
    return graph
from_nx(g: nx.Graph) -> BaseGraph classmethod

Convert a NetworkX Graph object into a QoolQit graph instance.

The input networkx.Graph graph must be defined only with the following allowed

Node attributes

pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers. weight: represents the node weight. Must be a real number.

Edge attributes: weight: represents the edge weight. Must be a real number.

Returns an instance of the class with following attributes
  • _node_weights : dict[node, float or None]
  • _edge_weights : dict[(u,v), float or None]
  • _coords : dict[node, (float,float) or None]
Source code in qoolqit/graphs/base_graph.py
@classmethod
def from_nx(cls, g: nx.Graph) -> BaseGraph:
    """Convert a NetworkX Graph object into a QoolQit graph instance.

    The input `networkx.Graph` graph must be defined only with the following allowed

    Node attributes:
        pos (tuple): represents the node 2D position. Must be a list/tuple of real numbers.
        weight: represents the node weight. Must be a real number.
    Edge attributes:
        weight: represents the edge weight. Must be a real number.

    Returns an instance of the class with following attributes:
        - _node_weights : dict[node, float or None]
        - _edge_weights : dict[(u,v), float or None]
        - _coords       : dict[node, (float,float) or None]
    """
    if not isinstance(g, nx.Graph):
        raise TypeError("Input must be a networkx.Graph instance.")

    g = nx.convert_node_labels_to_integers(g)
    num_nodes = len(g.nodes)
    num_edges = len(g.edges)

    # validate node attributes
    for name, data in g.nodes.data():
        unexpected_keys = set(data) - {"weight", "pos"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in node attributes.")

    node_pos = nx.get_node_attributes(g, "pos")
    if node_pos:
        if len(node_pos) != num_nodes:
            raise ValueError("Node attribute `pos` must be defined for all nodes")
        for name, pos in node_pos.items():
            is_2D = isinstance(pos, (tuple, list)) & (len(pos) == 2)
            is_real = all(isinstance(p, (float, int)) for p in pos)
            if not (is_2D & is_real):
                raise TypeError(
                    f"In node {name} the `pos` attribute must be a 2D tuple/list"
                    f" of real numbers, got {pos} instead."
                )
    node_weights = nx.get_node_attributes(g, "weight")
    if node_weights:
        if len(node_weights) != num_nodes:
            raise ValueError("Node attribute `weight` must be defined for all nodes")
        for name, weight in node_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In node {name} the `weight` attribute must be a real number, "
                    f"got {type(weight)} instead."
                    ""
                )

    # validate edge attributes
    for u, v, data in g.edges.data():
        unexpected_keys = set(data) - {"weight"}
        if unexpected_keys:
            raise ValueError(f"{unexpected_keys} not allowed in edge attributes.")
    edge_weights = nx.get_edge_attributes(g, "weight")
    if edge_weights:
        if len(edge_weights) != num_edges:
            raise ValueError("Edge attribute `weight` must be defined for all edges")
        for name, weight in edge_weights.items():
            if not isinstance(weight, (float, int)):
                raise TypeError(
                    f"In edge {name}, the attribute `weight` must be a real number, "
                    f"got {type(weight)} instead."
                )

    # build the instance of the graph
    graph = cls()
    graph.add_nodes_from(g.nodes)
    graph.add_edges_from(g.edges)
    graph._node_weights = nx.get_node_attributes(g, "weight", default=None)
    graph._coords = nx.get_node_attributes(g, "pos", default=None)
    graph._edge_weights = nx.get_edge_attributes(g, "weight", default=None)

    return graph
from_pyg(data: torch_geometric.data.Data, node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str | None = None, edge_weights_attr: str | None = None) -> DataGraph classmethod

Convert a PyTorch Geometric Data object into a DataGraph instance.

Requires torch_geometric. Uses to_networkx internally.

Default attributes copied (if present on data ):

  • Node: x, pos (pos is also stored in _coords)
  • Edge: edge_attr
  • Graph: y

Use node_attrs, edge_attrs, graph_attrs for extras.

QoolQit weights (_node_weights, _edge_weights) are not populated automatically — use the explicit parameters:

  • node_weights_attr: real-valued tensor of shape (N,) or (N, 1). Defaults to None.
  • edge_weights_attr: real-valued tensor of shape (E,) or (E, 1) where E = edge_index.shape[1] (directed count). Defaults to None.

The weight attribute is also stored as a regular node/edge attribute.

Parameters:

  • data (Data) –

    PyTorch Geometric Data object to convert.

  • node_attrs (Iterable[str] | None, default: None ) –

    extra node attributes to copy (beyond x and pos).

  • edge_attrs (Iterable[str] | None, default: None ) –

    extra edge attributes to copy (beyond edge_attr).

  • graph_attrs (Iterable[str] | None, default: None ) –

    extra graph-level attributes to copy (beyond y).

  • node_weights_attr (str | None, default: None ) –

    Data attribute to use as node weights.

  • edge_weights_attr (str | None, default: None ) –

    Data attribute to use as edge weights.

Returns:

  • DataGraph

    DataGraph with _coords, _node_weights, _edge_weights

  • DataGraph

    populated where applicable.

Raises:

  • ImportError

    if torch_geometric is not installed.

  • TypeError

    if data is not a torch_geometric.data.Data instance, or if a weight attribute is not a torch.Tensor.

  • AttributeError

    if a specified weight attribute is missing.

  • ValueError

    if a weight tensor has an incompatible shape or size.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def from_pyg(
    cls,
    data: torch_geometric.data.Data,
    node_attrs: Iterable[str] | None = None,
    edge_attrs: Iterable[str] | None = None,
    graph_attrs: Iterable[str] | None = None,
    node_weights_attr: str | None = None,
    edge_weights_attr: str | None = None,
) -> DataGraph:
    """Convert a PyTorch Geometric Data object into a DataGraph instance.

    Requires ``torch_geometric``. Uses ``to_networkx`` internally.

    **Default attributes copied (if present on** ``data`` **):**

    - Node: ``x``, ``pos`` (``pos`` is also stored in ``_coords``)
    - Edge: ``edge_attr``
    - Graph: ``y``

    Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.

    **QoolQit weights** (``_node_weights``, ``_edge_weights``) are not
    populated automatically — use the explicit parameters:

    - ``node_weights_attr``: real-valued tensor of shape ``(N,)`` or
      ``(N, 1)``. Defaults to ``None``.
    - ``edge_weights_attr``: real-valued tensor of shape ``(E,)`` or
      ``(E, 1)`` where ``E = edge_index.shape[1]`` (directed count).
      Defaults to ``None``.

    The weight attribute is also stored as a regular node/edge attribute.

    Arguments:
        data: PyTorch Geometric Data object to convert.
        node_attrs: extra node attributes to copy (beyond x and pos).
        edge_attrs: extra edge attributes to copy (beyond edge_attr).
        graph_attrs: extra graph-level attributes to copy (beyond y).
        node_weights_attr: Data attribute to use as node weights.
        edge_weights_attr: Data attribute to use as edge weights.

    Returns:
        DataGraph with ``_coords``, ``_node_weights``, ``_edge_weights``
        populated where applicable.

    Raises:
        ImportError: if ``torch_geometric`` is not installed.
        TypeError: if ``data`` is not a ``torch_geometric.data.Data``
            instance, or if a weight attribute is not a ``torch.Tensor``.
        AttributeError: if a specified weight attribute is missing.
        ValueError: if a weight tensor has an incompatible shape or size.
    """
    try:
        from torch_geometric.data import Data
        from torch_geometric.utils import to_networkx
    except ImportError as e:
        raise ImportError("Please, install the `torch_geometric` package.") from e

    if not isinstance(data, Data):
        raise TypeError("Input must be a torch_geometric.data.Data object.")

    # Validate weight attrs early and keep the squeezed tensors
    node_tensor = (
        cls._validate_weights_attr(data, node_weights_attr, data.num_nodes, "node")
        if node_weights_attr is not None
        else None
    )
    edge_tensor = (
        cls._validate_weights_attr(data, edge_weights_attr, data.num_edges, "edge")
        if edge_weights_attr is not None
        else None
    )

    # Select unique attributes and add default ones only if present in the data
    node_attrs_set = {k for k in {"x", "pos"} if k in data}
    if node_attrs is not None:
        node_attrs_set |= set(node_attrs)
    if node_weights_attr is not None:
        node_attrs_set.add(node_weights_attr)

    edge_attrs_set = {k for k in {"edge_attr"} if k in data}
    if edge_attrs is not None:
        edge_attrs_set |= set(edge_attrs)
    if edge_weights_attr is not None:
        edge_attrs_set.add(edge_weights_attr)

    graph_attrs_set = {k for k in {"y"} if k in data}
    if graph_attrs is not None:
        graph_attrs_set |= set(graph_attrs)

    # Convert to NetworkX (undirected, no self-loops)
    nx_graph = to_networkx(
        data,
        node_attrs=list(node_attrs_set),
        edge_attrs=list(edge_attrs_set),
        graph_attrs=list(graph_attrs_set),
        to_undirected=True,
        remove_self_loops=True,
    )

    # Build the DataGraph: edges carry their data, nodes carry their data
    graph = cls(nx_graph.edges(data=True))
    graph.add_nodes_from(nx_graph.nodes(data=True))
    graph.graph = nx_graph.graph

    # Re-initialize QoolQit internal dicts for all nodes/edges
    graph._coords = {n: None for n in graph.nodes}
    graph._reset_dicts()

    # pos → _coords (stored as list [x, y] by to_networkx)
    for node, node_data in nx_graph.nodes(data=True):
        if "pos" in node_data:
            graph._coords[node] = tuple(node_data["pos"])  # type: ignore[assignment]

    # node_weights_attr → _node_weights
    if node_tensor is not None:
        for i in range(data.num_nodes):
            graph._node_weights[i] = node_tensor[i].item()

    # edge_weights_attr → _edge_weights
    if edge_tensor is not None:
        seen: set = set()
        for idx in range(data.edge_index.shape[1]):
            u = int(data.edge_index[0, idx].item())
            v = int(data.edge_index[1, idx].item())
            key = (min(u, v), max(u, v))
            if key not in seen:
                graph._edge_weights[key] = edge_tensor[idx].item()
                seen.add(key)

    return graph
heavy_hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a heavy-hexagonal lattice graph, with respective coordinates.

Parameters:

  • m (int) –

    Number of rows of hexagons.

  • n (int) –

    Number of columns of hexagons.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Notes

The heavy-hexagonal lattice is a regular hexagonal lattice where each edge is decorated with an additional lattice site.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def heavy_hexagonal(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a heavy-hexagonal lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of hexagons.
        n: Number of columns of hexagons.
        spacing: The distance between adjacent nodes on the final lattice.

    Notes:
        The heavy-hexagonal lattice is a regular hexagonal lattice where
        each edge is decorated with an additional lattice site.
    """
    G_hex = nx.hexagonal_lattice_graph(m, n, with_positions=True)
    pos_unit = nx.get_node_attributes(G_hex, "pos")

    G_heavy = nx.Graph()
    scaling_factor = 2 * spacing

    label_map = {}
    for old_label, (x, y) in pos_unit.items():
        # Relabel to an even-integer grid to make space for midpoint nodes
        new_label = (2 * old_label[0], 2 * old_label[1])
        label_map[old_label] = new_label

        # Scale positions and add the node to the new graph
        new_pos = (x * scaling_factor, y * scaling_factor)
        G_heavy.add_node(new_label, pos=new_pos)

    for u_old, v_old in G_hex.edges():
        u_new, v_new = label_map[u_old], label_map[v_old]

        mid_label = ((u_new[0] + v_new[0]) // 2, (u_new[1] + v_new[1]) // 2)

        pos_u = G_heavy.nodes[u_new]["pos"]
        pos_v = G_heavy.nodes[v_new]["pos"]
        mid_pos = ((pos_u[0] + pos_v[0]) / 2, (pos_u[1] + pos_v[1]) / 2)

        G_heavy.add_node(mid_label, pos=mid_pos)
        G_heavy.add_edge(u_new, mid_label)
        G_heavy.add_edge(mid_label, v_new)

    final_nodes = sorted(list(G_heavy.nodes()))

    final_coords = [G_heavy.nodes[label]["pos"] for label in final_nodes]

    label_to_int = {label: i for i, label in enumerate(final_nodes)}

    final_edges = [(label_to_int[u], label_to_int[v]) for u, v in G_heavy.edges()]

    graph = cls.from_coordinates(final_coords)
    graph.add_edges_from(final_edges)
    graph._reset_dicts()
    return graph
hexagonal(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a hexagonal lattice graph, with respective coordinates.

Parameters:

  • m (int) –

    Number of rows of hexagons.

  • n (int) –

    Number of columns of hexagons.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def hexagonal(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a hexagonal lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of hexagons.
        n: Number of columns of hexagons.
        spacing: The distance between adjacent nodes on the final lattice.
    """
    G = nx.hexagonal_lattice_graph(m, n, with_positions=True)
    G = nx.convert_node_labels_to_integers(G)
    pos_unit = nx.get_node_attributes(G, "pos")

    final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}

    graph = cls.from_coordinates(final_pos)
    graph.add_edges_from(G.edges)
    graph._reset_dicts()
    return graph
interactions() -> dict

Rydberg model interaction 1/r^6 between pair of nodes.

Source code in qoolqit/graphs/base_graph.py
def interactions(self) -> dict:
    """Rydberg model interaction 1/r^6 between pair of nodes."""
    return {p: 1.0 / (r**6) for p, r in self.distances().items()}
is_ud_graph() -> bool

Check if the graph is unit-disk.

Source code in qoolqit/graphs/base_graph.py
def is_ud_graph(self) -> bool:
    """Check if the graph is unit-disk."""
    try:
        self.ud_radius_range()
        return True
    except ValueError:
        return False
line(n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a line graph, with the respective coordinates.

Parameters:

  • n (int) –

    number of nodes.

  • spacing (float, default: 1.0 ) –

    distance between each node.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def line(cls, n: int, spacing: float = 1.0) -> DataGraph:
    """Constructs a line graph, with the respective coordinates.

    Arguments:
        n: number of nodes.
        spacing: distance between each node.
    """
    coords = [(i * spacing, 0.0) for i in range(n)]
    graph = cls.from_coordinates(coords)
    edges = [(i, i + 1) for i in range(0, n - 1)]
    graph.add_edges_from(edges)
    graph._reset_dicts()
    return graph
max_distance(connected: bool | None = None) -> float

Returns the maximum distance in the graph.

Parameters:

  • connected (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def max_distance(self, connected: bool | None = None) -> float:
    """Returns the maximum distance in the graph.

    Arguments:
        connected: if True/False, computes only over connected/disconnected nodes.
    """
    distance: float
    if connected is None:
        distance = max(self.distances(self.all_node_pairs).values())
    elif connected:
        distance = max(self.distances(self.sorted_edges).values())
    else:
        distance = max(self.distances(self.all_node_pairs - self.sorted_edges).values())
    return distance
min_distance(connected: bool | None = None) -> float

Returns the minimum distance in the graph.

Parameters:

  • connected (bool | None, default: None ) –

    if True/False, computes only over connected/disconnected nodes.

Source code in qoolqit/graphs/base_graph.py
def min_distance(self, connected: bool | None = None) -> float:
    """Returns the minimum distance in the graph.

    Arguments:
        connected: if True/False, computes only over connected/disconnected nodes.
    """
    distance: float
    if connected is None:
        distance = min(self.distances(self.all_node_pairs).values())
    elif connected:
        distance = min(self.distances(self.sorted_edges).values())
    else:
        distance = min(self.distances(self.all_node_pairs - self.sorted_edges).values())
    return distance
random_er(n: int, p: float, seed: int | None = None) -> DataGraph classmethod

Constructs an Erdős–Rényi random graph.

Parameters:

  • n (int) –

    number of nodes.

  • p (float) –

    probability that any two nodes connect.

  • seed (int | None, default: None ) –

    random seed.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def random_er(cls, n: int, p: float, seed: int | None = None) -> DataGraph:
    """Constructs an Erdős–Rényi random graph.

    Arguments:
        n: number of nodes.
        p: probability that any two nodes connect.
        seed: random seed.
    """
    base_graph = nx.erdos_renyi_graph(n, p, seed)
    graph = DataGraph.from_nodes(list(base_graph.nodes))
    graph.add_edges_from(base_graph.edges)
    graph._reset_dicts()
    return graph
random_ud(n: int, radius: float = 1.0, L: float | None = None) -> DataGraph classmethod

Constructs a random unit-disk graph.

The nodes are sampled uniformly from a square of size (L x L). If L is not given, it is estimated based on a rough heuristic that of packing N nodes on a square of side L such that the expected minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).

Parameters:

  • n (int) –

    number of nodes.

  • radius (float, default: 1.0 ) –

    radius to use for defining the unit-disk edges.

  • L (float | None, default: None ) –

    size of the square on which to sample the node coordinates.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def random_ud(
    cls,
    n: int,
    radius: float = 1.0,
    L: float | None = None,
) -> DataGraph:
    """Constructs a random unit-disk graph.

    The nodes are sampled uniformly from a square of size (L x L).
    If L is not given, it is estimated based on a rough heuristic that
    of packing N nodes on a square of side L such that the expected
    minimum distance is R, leading to L ~ (R / 2) * sqrt(π * n).

    Arguments:
        n: number of nodes.
        radius: radius to use for defining the unit-disk edges.
        L: size of the square on which to sample the node coordinates.
    """
    if L is None:
        L = (radius / 2) * ((np.pi * n) ** 0.5)
    coords = random_coords(n, L)
    graph = cls.from_coordinates(coords)
    edges = graph.ud_edges(radius)
    graph.add_edges_from(edges)
    graph._reset_dicts()
    return graph
rescale_coords(*args: Any, scaling: float | None = None, spacing: float | None = None) -> None

Rescales the node coordinates by a factor.

Accepts either a scaling or a spacing factor.

Parameters:

  • scaling (float | None, default: None ) –

    value to scale by.

  • spacing (float | None, default: None ) –

    value to set as the minimum distance in the graph.

Source code in qoolqit/graphs/base_graph.py
def rescale_coords(
    self,
    *args: Any,
    scaling: float | None = None,
    spacing: float | None = None,
) -> None:
    """Rescales the node coordinates by a factor.

    Accepts either a scaling or a spacing factor.

    Arguments:
        scaling: value to scale by.
        spacing: value to set as the minimum distance in the graph.
    """
    if self.has_coords:
        msg = "Please pass either a `scaling` or a `spacing` value as a keyword argument."
        if (len(args) > 0) or (scaling is None and spacing is None):
            raise TypeError(msg)
        if scaling is None and spacing is not None:
            self._coords = space_coords(self._coords, spacing)
        elif spacing is None and scaling is not None:
            self._coords = scale_coords(self._coords, scaling)
        else:
            raise TypeError(msg)
    else:
        raise AttributeError("Trying to rescale coordinates on a graph without coordinates.")
set_ud_edges(radius: float) -> None

Reset the set of edges to be equal to the set of unit-disk edges.

Source code in qoolqit/graphs/data_graph.py
def set_ud_edges(self, radius: float) -> None:
    """Reset the set of edges to be equal to the set of unit-disk edges."""
    super().set_ud_edges(radius=radius)
    self._edge_weights = {e: None for e in self.sorted_edges}
square(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a square lattice graph, with respective coordinates.

Parameters:

  • m (int) –

    Number of rows of square.

  • n (int) –

    Number of columns of square.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def square(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a square lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of square.
        n: Number of columns of square.
        spacing: The distance between adjacent nodes on the final lattice.
    """
    G = nx.grid_2d_graph(m, n)
    final_coords = [(x * spacing, y * spacing) for (x, y) in list(G.nodes)]
    G = nx.convert_node_labels_to_integers(G)

    graph = DataGraph.from_coordinates(final_coords)
    graph.add_edges_from(G.edges)
    graph._reset_dicts()
    return graph
to_pyg(node_attrs: Iterable[str] | None = None, edge_attrs: Iterable[str] | None = None, graph_attrs: Iterable[str] | None = None, node_weights_attr: str = 'weight', edge_weights_attr: str = 'edge_weight') -> torch_geometric.data.Data

Convert the DataGraph to a PyTorch Geometric Data object.

Requires torch_geometric. Uses from_networkx internally.

Default attributes exported (if present on the graph):

  • Node "x"data.x; Edge "edge_attr"data.edge_attr
  • Graph "y"data.y

Use node_attrs, edge_attrs, graph_attrs for extras.

QoolQit internal dicts exported when populated:

  • _coordsdata.pos (float64, shape (N, 2))
  • _node_weightsdata.<node_weights_attr> (float64, shape (N,)). Defaults to "weight".
  • _edge_weightsdata.<edge_weights_attr> (float64, shape (2*E,)). Defaults to "edge_weight".

Parameters:

  • node_attrs (Iterable[str] | None, default: None ) –

    extra node attributes to export (beyond x).

  • edge_attrs (Iterable[str] | None, default: None ) –

    extra edge attributes to export (beyond edge_attr).

  • graph_attrs (Iterable[str] | None, default: None ) –

    extra graph-level attributes to export (beyond y).

  • node_weights_attr (str, default: 'weight' ) –

    Data attribute name for node weights. Defaults to "weight".

  • edge_weights_attr (str, default: 'edge_weight' ) –

    Data attribute name for edge weights. Defaults to "edge_weight".

Returns:

  • Data

    PyTorch Geometric Data object.

Raises:

  • ImportError

    if torch_geometric is not installed.

Source code in qoolqit/graphs/data_graph.py
def to_pyg(
    self,
    node_attrs: Iterable[str] | None = None,
    edge_attrs: Iterable[str] | None = None,
    graph_attrs: Iterable[str] | None = None,
    node_weights_attr: str = "weight",
    edge_weights_attr: str = "edge_weight",
) -> torch_geometric.data.Data:
    """Convert the DataGraph to a PyTorch Geometric Data object.

    Requires ``torch_geometric``. Uses ``from_networkx`` internally.

    **Default attributes exported (if present on the graph):**

    - Node ``"x"`` → ``data.x``; Edge ``"edge_attr"`` → ``data.edge_attr``
    - Graph ``"y"`` → ``data.y``

    Use ``node_attrs``, ``edge_attrs``, ``graph_attrs`` for extras.

    **QoolQit internal dicts exported when populated:**

    - ``_coords`` → ``data.pos`` (float64, shape ``(N, 2)``)
    - ``_node_weights`` → ``data.<node_weights_attr>`` (float64, shape
      ``(N,)``). Defaults to ``"weight"``.
    - ``_edge_weights`` → ``data.<edge_weights_attr>`` (float64, shape
      ``(2*E,)``). Defaults to ``"edge_weight"``.

    Arguments:
        node_attrs: extra node attributes to export (beyond x).
        edge_attrs: extra edge attributes to export (beyond edge_attr).
        graph_attrs: extra graph-level attributes to export (beyond y).
        node_weights_attr: Data attribute name for node weights.
            Defaults to ``"weight"``.
        edge_weights_attr: Data attribute name for edge weights.
            Defaults to ``"edge_weight"``.

    Returns:
        PyTorch Geometric Data object.

    Raises:
        ImportError: if ``torch_geometric`` is not installed.
    """
    try:
        import torch
        from torch_geometric.utils import from_networkx
    except ImportError as e:
        raise ImportError("Please, install the `torch_geometric` package.") from e

    node_attrs_set = set(node_attrs) if node_attrs else set()
    edge_attrs_set = set(edge_attrs) if edge_attrs else set()
    graph_attrs_list = list(graph_attrs) if graph_attrs else []

    # Add default PyG attributes if present in the graph
    if any("x" in d for _, d in self.nodes(data=True)):
        node_attrs_set.add("x")
    if any("edge_attr" in d for _, _, d in self.edges(data=True)):
        edge_attrs_set.add("edge_attr")
    if "y" in self.graph:
        graph_attrs_list.append("y")

    # Build a filtered copy with only the requested attributes
    filtered_graph = nx.Graph()
    filtered_graph.add_nodes_from(self.nodes())
    filtered_graph.add_edges_from(self.edges())

    for node, node_data in self.nodes(data=True):
        for key, value in node_data.items():
            if key in node_attrs_set:
                filtered_graph.nodes[node][key] = value
    for u, v, edge_data in self.edges(data=True):
        for key, value in edge_data.items():
            if key in edge_attrs_set:
                filtered_graph.edges[u, v][key] = value
    for attr in graph_attrs_list:
        if attr in self.graph:
            filtered_graph.graph[attr] = self.graph[attr]

    data = from_networkx(filtered_graph)

    # Export _coords → pos
    if self.has_coords:
        positions = [self._coords[n] for n in sorted(self.nodes())]
        data.pos = torch.tensor(positions, dtype=torch.float64)

    # Export _node_weights → node_weights_attr
    if self.has_node_weights:
        weights = [self._node_weights[n] for n in sorted(self.nodes())]
        setattr(data, node_weights_attr, torch.tensor(weights, dtype=torch.float64))

    # Export _edge_weights → edge_weights_attr (one value per directed edge in edge_index)
    if self.has_edge_weights:
        edge_weights: list[float] = []
        for i in range(data.edge_index.shape[1]):
            u, v = int(data.edge_index[0, i].item()), int(data.edge_index[1, i].item())
            edge_key = (min(u, v), max(u, v))
            edge_weights.append(float(self._edge_weights[edge_key]))  # type: ignore[arg-type]
        setattr(data, edge_weights_attr, torch.tensor(edge_weights, dtype=torch.float64))

    return data
triangular(m: int, n: int, spacing: float = 1.0) -> DataGraph classmethod

Constructs a triangular lattice graph, with respective coordinates.

Parameters:

  • m (int) –

    Number of rows of triangles.

  • n (int) –

    Number of columns of triangles.

  • spacing (float, default: 1.0 ) –

    The distance between adjacent nodes on the final lattice.

Source code in qoolqit/graphs/data_graph.py
@classmethod
def triangular(
    cls,
    m: int,
    n: int,
    spacing: float = 1.0,
) -> DataGraph:
    """
    Constructs a triangular lattice graph, with respective coordinates.

    Arguments:
        m: Number of rows of triangles.
        n: Number of columns of triangles.
        spacing: The distance between adjacent nodes on the final lattice.
    """
    G = nx.triangular_lattice_graph(m, n, with_positions=True)
    G = nx.convert_node_labels_to_integers(G)
    pos_unit = nx.get_node_attributes(G, "pos")

    final_pos = {node: (x * spacing, y * spacing) for node, (x, y) in pos_unit.items()}

    graph = cls.from_coordinates(final_pos)
    graph.add_edges_from(G.edges)
    graph._reset_dicts()
    return graph
ud_edges(radius: float) -> set

Returns the set of edges given by the intersection of circles of a given radius.

Parameters:

  • radius (float) –

    the value

Source code in qoolqit/graphs/base_graph.py
def ud_edges(self, radius: float) -> set:
    """Returns the set of edges given by the intersection of circles of a given radius.

    Arguments:
        radius: the value
    """
    if self.has_coords:
        return set(e for e, d in self.distances().items() if less_or_equal(d, radius))
    else:
        raise AttributeError("Getting unit disk edges is not valid without coordinates.")
ud_radius_range() -> tuple

Return the range (R_min, R_max) where the graph is unit-disk.

The graph is unit-disk if the maximum distance between all connected nodes is smaller than the minimum distance between disconnected nodes. This means that for any value R in that interval, the following condition is true:

graph.ud_edges(radius = R) == graph.sorted edges

Source code in qoolqit/graphs/base_graph.py
def ud_radius_range(self) -> tuple:
    """Return the range (R_min, R_max) where the graph is unit-disk.

    The graph is unit-disk if the maximum distance between all connected nodes is
    smaller than the minimum distance between disconnected nodes. This means that
    for any value R in that interval, the following condition is true:

    graph.ud_edges(radius = R) == graph.sorted edges
    """
    if self.has_coords:
        n_edges = len(self.sorted_edges)
        if n_edges == 0:
            # If the graph is empty and has coordinates
            return (0.0, self.min_distance(connected=False))
        elif n_edges == len(self.all_node_pairs):
            # If the graph is fully connected
            return (self.max_distance(connected=True), float("inf"))
        elif self.max_distance(connected=True) < self.min_distance(connected=False):
            return (self.max_distance(connected=True), self.min_distance(connected=False))
        else:
            raise ValueError("Graph is not unit disk.")
    else:
        raise AttributeError("Checking if graph is unit disk is not valid without coordinates.")

utils

Functions:

  • all_node_pairs

    Return all pairs of nodes (u, v) where u < v.

  • distances

    Return a dictionary of edge distances.

  • less_or_equal

    Less or approximately equal.

  • radial_distances

    Return a dictionary of node distances from the origin.

  • random_coords

    Generate a random set of node coordinates on a square of side L.

  • random_edge_list

    Generates a random set of k edges linkings items from a set of nodes.

  • scale_coords

    Scale the coordinates by a given value.

  • space_coords

    Spaces the coordinates so the minimum distance is equal to a set spacing.

all_node_pairs(nodes: Iterable) -> set

Return all pairs of nodes (u, v) where u < v.

Parameters:

  • nodes
    (Iterable) –

    set of node indices.

Source code in qoolqit/graphs/utils.py
def all_node_pairs(nodes: Iterable) -> set:
    """Return all pairs of nodes (u, v) where u < v.

    Arguments:
        nodes: set of node indices.
    """
    return set(filter(lambda x: x[0] < x[1], product(nodes, nodes)))
distances(coords: dict, edge_list: Iterable) -> dict

Return a dictionary of edge distances.

Parameters:

  • coords
    (dict) –

    dictionary of node coordinates.

  • edge_list
    (Iterable) –

    edge list to compute the distances for.

Source code in qoolqit/graphs/utils.py
def distances(coords: dict, edge_list: Iterable) -> dict:
    """Return a dictionary of edge distances.

    Arguments:
        coords: dictionary of node coordinates.
        edge_list: edge list to compute the distances for.
    """
    return {edge: dist(coords[edge[0]], coords[edge[1]]) for edge in edge_list}
less_or_equal(a: float, b: float, rel_tol: float = 0.0, abs_tol: float = ATOL_32) -> bool

Less or approximately equal.

Source code in qoolqit/graphs/utils.py
def less_or_equal(a: float, b: float, rel_tol: float = 0.0, abs_tol: float = ATOL_32) -> bool:
    """Less or approximately equal."""
    return a < b or isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol)
radial_distances(coords: dict) -> dict

Return a dictionary of node distances from the origin.

Parameters:

  • coords
    (dict) –

    dictionary of node coordinates.

Source code in qoolqit/graphs/utils.py
def radial_distances(coords: dict) -> dict:
    """Return a dictionary of node distances from the origin.

    Arguments:
        coords: dictionary of node coordinates.
    """
    return {key: hypot(*node) for key, node in coords.items()}
random_coords(n: int, L: float = 1.0) -> list

Generate a random set of node coordinates on a square of side L.

Parameters:

  • n
    (int) –

    number of coordinate pairs to generate.

  • L
    (float, default: 1.0 ) –

    side of the square.

Source code in qoolqit/graphs/utils.py
def random_coords(n: int, L: float = 1.0) -> list:
    """Generate a random set of node coordinates on a square of side L.

    Arguments:
        n: number of coordinate pairs to generate.
        L: side of the square.
    """
    x_coords = np.random.uniform(low=-L / 2, high=L / 2, size=(n,)).tolist()
    y_coords = np.random.uniform(low=-L / 2, high=L / 2, size=(n,)).tolist()
    return [(x, y) for x, y in zip(x_coords, y_coords)]
random_edge_list(nodes: Iterable, k: int) -> list

Generates a random set of k edges linkings items from a set of nodes.

Source code in qoolqit/graphs/utils.py
def random_edge_list(nodes: Iterable, k: int) -> list:
    """Generates a random set of k edges linkings items from a set of nodes."""
    all_edges = all_node_pairs(nodes)
    return random.sample(tuple(all_edges), k=k)
scale_coords(coords: dict, scaling: float) -> dict

Scale the coordinates by a given value.

Parameters:

  • coords
    (dict) –

    dictionary of node coordinates.

  • scaling
    (float) –

    value to scale by.

Source code in qoolqit/graphs/utils.py
def scale_coords(coords: dict, scaling: float) -> dict:
    """Scale the coordinates by a given value.

    Arguments:
        coords: dictionary of node coordinates.
        scaling: value to scale by.
    """
    scaled_coords = {i: (c[0] * scaling, c[1] * scaling) for i, c in coords.items()}
    return scaled_coords
space_coords(coords: dict, spacing: float) -> dict

Spaces the coordinates so the minimum distance is equal to a set spacing.

Parameters:

  • coords
    (dict) –

    dictionary of node coordinates.

  • spacing
    (float) –

    value to set as minimum distance.

Source code in qoolqit/graphs/utils.py
def space_coords(coords: dict, spacing: float) -> dict:
    """Spaces the coordinates so the minimum distance is equal to a set spacing.

    Arguments:
        coords: dictionary of node coordinates.
        spacing: value to set as minimum distance.
    """
    pairs = all_node_pairs(list(coords.keys()))
    min_dist = min(distances(coords, pairs).values())
    scale_factor = spacing / min_dist
    return scale_coords(coords, scale_factor)

program

Classes:

  • QuantumProgram

    A program representing a Sequence acting on a Register of qubits.

QuantumProgram(register: Register, drive: Drive)

A program representing a Sequence acting on a Register of qubits.

Parameters:

  • register
    (Register) –

    the register of qubits, defining their positions.

  • drive
    (Drive) –

    the drive acting on qubits, defining amplitude, detuning and phase.

Methods:

  • compile_to

    Compiles the quantum program for execution on a specific device.

Attributes:

Source code in qoolqit/program.py
def __init__(
    self,
    register: Register,
    drive: Drive,
) -> None:

    if not isinstance(register, Register):
        raise TypeError("`register` must be of type Register.")
    self._register = register
    if not isinstance(drive, Drive):
        raise TypeError("`drive` must be of type Drive.")
    self._drive = drive
    self._compiled_sequence: PulserSequence | None = None
    for detuning in drive.weighted_detunings:
        for key in detuning.weights.keys():
            if key not in register.qubits:
                raise ValueError(
                    "In this QuantumProgram, the drive and the register "
                    f"do not match: qubit {key} appears in the drive but "
                    "is not defined in the register."
                )
compiled_sequence: PulserSequence property

The Pulser sequence compiled to a specific device.

drive: Drive property

The driving waveforms.

is_compiled: bool property

Check if the program has been compiled.

register: Register property

The register of qubits.

compile_to(device: Device, profile: CompilerProfile = CompilerProfile.MAX_ENERGY, device_max_duration_ratio: float | None = None) -> None

Compiles the quantum program for execution on a specific device.

The compilation process adapts the program to the device's constraints while preserving the relative ratios of the original program parameters. Different compilation profiles optimize for specific objectives:

  • CompilerProfile.MAX_ENERGY (default): Scales the program to utilize the device's maximum capabilities. The drive amplitude and the register positions are rescaled to achieve respectively the maximum amplitude and the minimum pairwise distance compatible with the input program and the device's constraints.
  • CompilerProfile.WORKING_POINT: .

Further options DO NOT preserve the input program, but rather adapts the program to the device's constraint. Programs compiled this way are not guaranteed to be portable across devices.

  • device_max_duration_ratio: Rescale the drive duration to a fraction of the device's maximum allowed duration. This option is useful in adiabatic protocols where one simply seek to minimize the time derivative of the drive's amplitude.

Parameters:

  • device
    (Device) –

    The target device for compilation.

  • profile
    (CompilerProfile, default: MAX_ENERGY ) –

    The compilation strategy to optimize the program. Defaults to CompilerProfile.MAX_ENERGY.

  • device_max_duration_ratio
    (float | None, default: None ) –

    Whether to set the program duration to a fraction of the device's maximum allowed duration. Must be a number in the range (0, 1]. Can only be set if the device has a maximum allowed duration.

Raises:

  • CompilationError

    If the compilation fails due to device constraints.

Source code in qoolqit/program.py
def compile_to(
    self,
    device: Device,
    profile: CompilerProfile = CompilerProfile.MAX_ENERGY,
    device_max_duration_ratio: float | None = None,
) -> None:
    """Compiles the quantum program for execution on a specific device.

    The compilation process adapts the program to the device's constraints while
    preserving the relative ratios of the original program parameters. Different
    compilation profiles optimize for specific objectives:

    - CompilerProfile.MAX_ENERGY (default): Scales the program to utilize the device's
        maximum capabilities. The drive amplitude and the register positions are rescaled
        to achieve respectively the maximum amplitude and the minimum pairwise distance
        compatible with the input program and the device's constraints.
    - CompilerProfile.WORKING_POINT: .

    Further options DO NOT preserve the input program, but rather adapts the program to
    the device's constraint. Programs compiled this way are not guaranteed to be portable
    across devices.

    - device_max_duration_ratio: Rescale the drive duration to a fraction of the
        device's maximum allowed duration.
        This option is useful in adiabatic protocols where one simply seek to
        minimize the time derivative of the drive's amplitude.

    Args:
        device: The target device for compilation.
        profile: The compilation strategy to optimize the program.
            Defaults to CompilerProfile.MAX_ENERGY.
        device_max_duration_ratio: Whether to set the program duration to a fraction of
            the device's maximum allowed duration. Must be a number in the range (0, 1].
            Can only be set if the device has a maximum allowed duration.

    Raises:
        CompilationError: If the compilation fails due to device constraints.
    """

    if device_max_duration_ratio is not None:
        if device._max_duration is None:
            raise ValueError(
                "Cannot set `device_max_duration_ratio` because the target device "
                "does not have a maximum allowed duration."
            )
        if not (0 < device_max_duration_ratio <= 1):
            raise ValueError(
                "`device_max_duration_ratio` must be between 0 and 1, "
                f"got {device_max_duration_ratio} instead."
            )

    compiler = SequenceCompiler(
        self.register, self.drive, device, profile, device_max_duration_ratio
    )
    self._device = device
    self._compiled_sequence = compiler.compile_sequence()

register

Classes:

  • Register

    The Register in QoolQit, representing a set of qubits with coordinates.

Register(qubits: dict)

The Register in QoolQit, representing a set of qubits with coordinates.

Parameters:

  • qubits
    (dict) –

    a dictionary of qubits and respective coordinates {q: (x, y), ...}.

Methods:

Attributes:

  • n_qubits (int) –

    Number of qubits in the Register.

  • qubits (dict) –

    Returns the dictionary of qubits and respective coordinates.

  • qubits_ids (list) –

    Returns the qubit keys.

Source code in qoolqit/register.py
def __init__(self, qubits: dict) -> None:
    """Default constructor for the Register.

    Arguments:
        qubits: a dictionary of qubits and respective coordinates {q: (x, y), ...}.
    """
    if not isinstance(qubits, dict):
        raise TypeError(
            "Register must be initialized with a dictionary of "
            "qubits and respective coordinates {q: (x, y), ...}."
        )

    self._qubits: dict = qubits
n_qubits: int property

Number of qubits in the Register.

qubits: dict property

Returns the dictionary of qubits and respective coordinates.

qubits_ids: list property

Returns the qubit keys.

distances() -> dict

Distance between each qubit pair.

Source code in qoolqit/register.py
def distances(self) -> dict:
    """Distance between each qubit pair."""
    pairs = all_node_pairs(list(self.qubits.keys()))
    return distances(self.qubits, pairs)
draw(return_fig: bool = False) -> Figure | None

Draw the register.

Parameters:

  • return_fig
    (bool, default: False ) –

    boolean argument to return the matplotlib figure.

Source code in qoolqit/register.py
def draw(self, return_fig: bool = False) -> Figure | None:
    """Draw the register.

    Arguments:
        return_fig: boolean argument to return the matplotlib figure.
    """
    fig, ax = plt.subplots(1, 1, figsize=(4, 4), dpi=150)
    ax.set_aspect("equal")

    x_coords, y_coords = zip(*self.qubits.values())
    x_min, x_max = min(x_coords), max(x_coords)
    y_min, y_max = min(y_coords), max(y_coords)

    grid_x_min, grid_x_max = min(-1, x_min), max(1, x_max)
    grid_y_min, grid_y_max = min(-1, y_min), max(1, y_max)

    grid_scale = ceil(max(grid_x_max - grid_x_min, grid_y_max - grid_y_min))

    ax.grid(True, color="lightgray", linestyle="--", linewidth=0.7)
    ax.set_axisbelow(True)
    ax.set_xlabel("x")
    ax.set_ylabel("y")

    eps = 0.05 * grid_scale
    ax.set_xlim(grid_x_min - eps, grid_x_max + eps)
    ax.set_ylim(grid_y_min - eps, grid_y_max + eps)

    possible_multiples = [0.2, 0.25, 0.5, 1.0, 2.0, 2.5, 5.0, 10.0]
    grid_multiple = min(possible_multiples, key=lambda x: abs(x - grid_scale / 8))
    majorLocatorX = MultipleLocator(grid_multiple)
    majorLocatorY = MultipleLocator(grid_multiple)
    ax.xaxis.set_major_locator(majorLocatorX)
    ax.yaxis.set_major_locator(majorLocatorY)

    ax.scatter(x_coords, y_coords, s=50, color="darkgreen")

    ax.tick_params(axis="both", which="both", labelbottom=True, labelleft=True, labelsize=8)

    if return_fig:
        plt.close()
        return fig
    else:
        return None
from_coordinates(coords: list) -> Register classmethod

Initializes a Register from a list of coordinates.

Parameters:

  • coords
    (list) –

    a list of coordinates [(x, y), ...]

Source code in qoolqit/register.py
@classmethod
def from_coordinates(cls, coords: list) -> Register:
    """Initializes a Register from a list of coordinates.

    Arguments:
        coords: a list of coordinates [(x, y), ...]
    """
    if not isinstance(coords, list):
        raise TypeError(
            "Register must be initialized with a dictionary of qubit and coordinates."
        )
    coords_dict = {i: pos for i, pos in enumerate(coords)}
    return cls(coords_dict)
from_graph(graph: DataGraph) -> Register classmethod

Initializes a Register from a graph that has coordinates.

Parameters:

  • graph
    (DataGraph) –

    a DataGraph instance.

Source code in qoolqit/register.py
@classmethod
def from_graph(cls, graph: DataGraph) -> Register:
    """Initializes a Register from a graph that has coordinates.

    Arguments:
        graph: a DataGraph instance.
    """

    if not graph.has_coords:
        raise ValueError("Initializing a register from a graph requires node coordinates.")

    if len(graph.nodes) == 0:
        raise ValueError("Trying to initialize a register from an empty graph.")

    return cls(graph.coords)
interactions() -> dict

Interaction 1/r^6 between each qubit pair.

Source code in qoolqit/register.py
def interactions(self) -> dict:
    """Interaction 1/r^6 between each qubit pair."""
    return {p: 1.0 / (r**6) for p, r in self.distances().items()}
max_radial_distance() -> float

Maximum radial distance between all qubits.

Source code in qoolqit/register.py
def max_radial_distance(self) -> float:
    """Maximum radial distance between all qubits."""
    max_radial_distance: float = max(self.radial_distances().values())
    return max_radial_distance
min_distance() -> float

Minimum distance between all qubit pairs.

Source code in qoolqit/register.py
def min_distance(self) -> float:
    """Minimum distance between all qubit pairs."""
    distance: float = min(self.distances().values())
    return distance
radial_distances() -> dict

Radial distance of each qubit from the origin.

Source code in qoolqit/register.py
def radial_distances(self) -> dict:
    """Radial distance of each qubit from the origin."""
    return radial_distances(self.qubits)

waveforms

Modules:

Classes:

  • Blackman

    A Blackman window of a specified duration and area under the curve.

  • Constant

    A constant waveform over a given duration.

  • Delay

    An empty waveform.

  • Interpolated

    A waveform created from interpolation of a set of data points.

  • PiecewiseLinear

    A piecewise linear waveform.

  • Ramp

    A ramp that linearly interpolates between an initial and final value.

  • Sin

    An arbitrary sine over a given duration.

Blackman(duration: float, area: float)

A Blackman window of a specified duration and area under the curve.

Implements the Blackman window shaped waveform blackman(t) = A(0.42 - 0.5cos(αt) + 0.08cos(2αt)) A = area/(0.42duration) α = 2π/duration

See: https://en.wikipedia.org/wiki/Window_function#:~:text=Blackman%20window

Parameters:

  • duration
    (float) –

    The waveform duration.

  • area
    (float) –

    The integral of the waveform.

Example
blackman_wf = Blackman(100.0, area=3.14)

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(self, duration: float, area: float) -> None:
    """Initializes a new Blackman waveform."""
    super().__init__(duration, area=area)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

Constant(duration: float, value: float)

A constant waveform over a given duration.

Parameters:

  • duration
    (float) –

    the total duration.

  • value
    (float) –

    the value to take during the duration.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    value: float,
) -> None:
    super().__init__(duration, value=value)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

Delay(duration: float, *args: float, **kwargs: float | np.ndarray)

An empty waveform.

Parameters:

  • duration
    (float) –

    the total duration of the waveform.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/base_waveforms.py
def __init__(
    self,
    duration: float,
    *args: float,
    **kwargs: float | np.ndarray,
) -> None:
    """Initializes the Waveform.

    Arguments:
        duration: the total duration of the waveform.
    """

    if duration <= 0:
        raise ValueError("Duration needs to be a positive non-zero value.")

    if len(args) > 0:
        raise ValueError(
            f"Extra arguments in {type(self).__name__} need to be passed as keyword arguments"
        )

    self._duration = duration
    self._params_dict = kwargs

    self._max: float | None = None
    self._min: float | None = None

    for key, value in kwargs.items():
        setattr(self, key, value)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

Interpolated(duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = 'PchipInterpolator', **interpolator_kwargs: Any)

A waveform created from interpolation of a set of data points.

Parameters:

  • duration
    (int) –

    The waveform duration (in ns).

  • values
    (ArrayLike) –

    Values of the interpolation points. Must be a list of castable to float or a parametrized object.

  • times
    (ArrayLike, default: None ) –

    Fractions of the total duration (between 0 and 1), indicating where to place each value on the time axis. Must be a list of castable to float or a parametrized object. If not given, the values are spread evenly throughout the full duration of the waveform.

  • interpolator
    (str, default: 'PchipInterpolator' ) –

    The SciPy interpolation class to use. Supports "PchipInterpolator" and "interp1d".

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    values: ArrayLike,
    times: Optional[ArrayLike] = None,
    interpolator: str = "PchipInterpolator",
    **interpolator_kwargs: Any,
):
    """Initializes a new Interpolated waveform."""
    super().__init__(duration)
    self._values = np.array(values, dtype=float)
    if times:  # fractional times in [0,1]
        if any([(ft < 0) or (ft > 1) for ft in times]):
            raise ValueError("All values in `times` must be in [0,1].")
        self._times = np.array(times, dtype=float)
        if len(times) != len(self._values):
            raise ValueError(
                "Arguments `values` and `times` must be arrays of the same length."
            )
    else:
        self._times = np.linspace(0, 1, num=len(self._values))

    if interpolator not in self._valid_interpolators:
        raise ValueError(
            f"Invalid interpolator '{interpolator}', only "
            "accepts: " + ", ".join(self._valid_interpolators)
        )
    self._interpolator = interpolator
    self._interpolator_kwargs = interpolator_kwargs

    interp_cls = getattr(interpolate, interpolator)
    self._interp_func = interp_cls(duration * self._times, self._values, **interpolator_kwargs)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

PiecewiseLinear(durations: list | tuple, values: list | tuple)

A piecewise linear waveform.

Creates a composite waveform of N ramps that linearly interpolate through the given N+1 values.

Parameters:

  • durations
    (list | tuple) –

    list or tuple of N duration values.

  • values
    (list | tuple) –

    list or tuple of N+1 waveform values.

Methods:

  • function

    Identifies the right waveform in the composition and evaluates it at time t.

  • max

    Get the maximum value of the waveform.

  • min

    Get the approximate minimum value of the waveform.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • durations (list[float]) –

    Returns the list of durations of each individual waveform.

  • n_waveforms (int) –

    Returns the number of waveforms.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

  • times (list[float]) –

    Returns the list of times when each individual waveform starts.

  • waveforms (list[Waveform]) –

    Returns a list of the individual waveforms.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    durations: list | tuple,
    values: list | tuple,
) -> None:
    if not (isinstance(durations, (list, tuple)) or isinstance(values, (list, tuple))):
        raise TypeError(
            "A PiecewiseLinear waveform requires a list or tuple of durations and values."
        )

    if len(durations) + 1 != len(values) or len(durations) == 1:
        raise ValueError(
            "A PiecewiseLinear waveform requires N durations and N + 1 values, for N >= 2."
        )

    for duration in durations:
        if duration == 0.0:
            raise ValueError("A PiecewiseLinear interval cannot have zero duration.")

    self.values = values

    wfs = [Ramp(dur, values[i], values[i + 1]) for i, dur in enumerate(durations)]

    super().__init__(*wfs)
duration: float property

Returns the duration of the waveform.

durations: list[float] property

Returns the list of durations of each individual waveform.

n_waveforms: int property

Returns the number of waveforms.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

times: list[float] property

Returns the list of times when each individual waveform starts.

waveforms: list[Waveform] property

Returns a list of the individual waveforms.

function(t: float) -> float

Identifies the right waveform in the composition and evaluates it at time t.

Source code in qoolqit/waveforms/base_waveforms.py
def function(self, t: float) -> float:
    """Identifies the right waveform in the composition and evaluates it at time t."""
    idx = np.searchsorted(self.times, t, side="right") - 1
    if idx == -1:
        return 0.0
    if idx == self.n_waveforms:
        if t == self.times[-1]:
            idx = idx - 1
        else:
            return 0.0

    local_t = t - self.times[idx]
    value: float = self.waveforms[idx](local_t)
    return value
max() -> float

Get the maximum value of the waveform.

Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float:
    """Get the maximum value of the waveform."""
    return max([wf.max() for wf in self.waveforms])
min() -> float

Get the approximate minimum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float:
    """Get the approximate minimum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the minimum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._min is None:
        self._approximate_min_max()
    return cast(float, self._min)

Ramp(duration: float, initial_value: float, final_value: float)

A ramp that linearly interpolates between an initial and final value.

Parameters:

  • duration
    (float) –

    the total duration.

  • initial_value
    (float) –

    the initial value at t = 0.

  • final_value
    (float) –

    the final value at t = duration.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    initial_value: float,
    final_value: float,
) -> None:
    super().__init__(duration, initial_value=initial_value, final_value=final_value)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

Sin(duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0)

An arbitrary sine over a given duration.

Parameters:

  • duration
    (float) –

    the total duration.

  • amplitude
    (float, default: 1.0 ) –

    the amplitude of the sine wave.

  • omega
    (float, default: 1.0 ) –

    the frequency of the sine wave.

  • phi
    (float, default: 0.0 ) –

    the phase of the sine wave.

  • shift
    (float, default: 0.0 ) –

    the vertical shift of the sine wave.

Methods:

  • max

    Get the approximate maximum value of the waveform.

  • min

    Get the approximate minimum value of the waveform.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    amplitude: float = 1.0,
    omega: float = 1.0,
    phi: float = 0.0,
    shift: float = 0.0,
) -> None:
    super().__init__(duration, amplitude=amplitude, omega=omega, phi=phi, shift=shift)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

max() -> float

Get the approximate maximum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float:
    """Get the approximate maximum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the maximum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._max is None:
        self._approximate_min_max()
    return cast(float, self._max)
min() -> float

Get the approximate minimum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float:
    """Get the approximate minimum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the minimum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._min is None:
        self._approximate_min_max()
    return cast(float, self._min)

base_waveforms

Classes:

CompositeWaveform(*waveforms: Waveform)

Base class for composite waveforms.

A CompositeWaveform stores a sequence of waveforms occurring one after the other by the order given. When it is evaluated at time t, the corresponding waveform from the sequence is identified depending on the duration of each one, and it is then evaluated for a time t' = t minus the duration of all previous waveforms.

Parameters:

  • waveforms
    (Waveform, default: () ) –

    an iterator over waveforms.

Methods:

  • function

    Identifies the right waveform in the composition and evaluates it at time t.

  • max

    Get the maximum value of the waveform.

  • min

    Get the approximate minimum value of the waveform.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • durations (list[float]) –

    Returns the list of durations of each individual waveform.

  • n_waveforms (int) –

    Returns the number of waveforms.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

  • times (list[float]) –

    Returns the list of times when each individual waveform starts.

  • waveforms (list[Waveform]) –

    Returns a list of the individual waveforms.

Source code in qoolqit/waveforms/base_waveforms.py
def __init__(self, *waveforms: Waveform) -> None:
    """Initializes the CompositeWaveform.

    Arguments:
        waveforms: an iterator over waveforms.
    """
    if not all(isinstance(wf, Waveform) for wf in waveforms):
        raise TypeError("All arguments must be instances of Waveform.")
    if not waveforms:
        raise ValueError("At least one Waveform must be provided.")

    self._waveforms = []
    for wf in waveforms:
        if isinstance(wf, CompositeWaveform):
            self._waveforms += wf.waveforms
        else:
            self._waveforms.append(wf)

    super().__init__(sum(self.durations))
duration: float property

Returns the duration of the waveform.

durations: list[float] property

Returns the list of durations of each individual waveform.

n_waveforms: int property

Returns the number of waveforms.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

times: list[float] property

Returns the list of times when each individual waveform starts.

waveforms: list[Waveform] property

Returns a list of the individual waveforms.

function(t: float) -> float

Identifies the right waveform in the composition and evaluates it at time t.

Source code in qoolqit/waveforms/base_waveforms.py
def function(self, t: float) -> float:
    """Identifies the right waveform in the composition and evaluates it at time t."""
    idx = np.searchsorted(self.times, t, side="right") - 1
    if idx == -1:
        return 0.0
    if idx == self.n_waveforms:
        if t == self.times[-1]:
            idx = idx - 1
        else:
            return 0.0

    local_t = t - self.times[idx]
    value: float = self.waveforms[idx](local_t)
    return value
max() -> float

Get the maximum value of the waveform.

Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float:
    """Get the maximum value of the waveform."""
    return max([wf.max() for wf in self.waveforms])
min() -> float

Get the approximate minimum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float:
    """Get the approximate minimum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the minimum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._min is None:
        self._approximate_min_max()
    return cast(float, self._min)
Waveform(duration: float, *args: float, **kwargs: float | np.ndarray)

Base class for waveforms.

A Waveform is a function of time for t >= 0. Custom waveforms can be defined by inheriting from the base class and overriding the function method corresponding to the function f(t) that returns the value of the waveform evaluated at time t.

A waveform is always a 1D function, so if it includes other parameters, these should be passed and saved at initialization for usage within the function method.

Parameters:

  • duration
    (float) –

    the total duration of the waveform.

Methods:

  • function

    Evaluates the waveform function at a given time t.

  • max

    Get the approximate maximum value of the waveform.

  • min

    Get the approximate minimum value of the waveform.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/base_waveforms.py
def __init__(
    self,
    duration: float,
    *args: float,
    **kwargs: float | np.ndarray,
) -> None:
    """Initializes the Waveform.

    Arguments:
        duration: the total duration of the waveform.
    """

    if duration <= 0:
        raise ValueError("Duration needs to be a positive non-zero value.")

    if len(args) > 0:
        raise ValueError(
            f"Extra arguments in {type(self).__name__} need to be passed as keyword arguments"
        )

    self._duration = duration
    self._params_dict = kwargs

    self._max: float | None = None
    self._min: float | None = None

    for key, value in kwargs.items():
        setattr(self, key, value)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

function(t: float) -> float abstractmethod

Evaluates the waveform function at a given time t.

Source code in qoolqit/waveforms/base_waveforms.py
@abstractmethod
def function(self, t: float) -> float:
    """Evaluates the waveform function at a given time t."""
    ...
max() -> float

Get the approximate maximum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float:
    """Get the approximate maximum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the maximum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._max is None:
        self._approximate_min_max()
    return cast(float, self._max)
min() -> float

Get the approximate minimum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float:
    """Get the approximate minimum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the minimum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._min is None:
        self._approximate_min_max()
    return cast(float, self._min)

utils

Functions:

  • round_to_sum

    Round a list of numbers such that their sum is the rounded sum.

round_to_sum(values: list[float]) -> list[int]

Round a list of numbers such that their sum is the rounded sum.

Σᵢround(aᵢ) = round(Σᵢaᵢ)

Example
>>> round_to_sum([100.3, 100.3, 100.4])
>>> [100, 100, 101]
Source code in qoolqit/waveforms/utils.py
def round_to_sum(values: list[float]) -> list[int]:
    """Round a list of numbers such that their sum is the rounded sum.

    Σᵢround(aᵢ) = round(Σᵢaᵢ)

    Example:
        ```python
        >>> round_to_sum([100.3, 100.3, 100.4])
        >>> [100, 100, 101]
        ```
    """
    rounded_values = [round(el) for el in values]
    reminders = [el - rel for rel, el in zip(rounded_values, values)]
    sum_reminders = round(sum(reminders))
    p = np.argsort(reminders)

    for i in range(abs(sum_reminders)):
        if sum_reminders < 0:
            rounded_values[p[i]] -= 1
        if sum_reminders > 0:
            rounded_values[p[-1 - i]] += 1

    return rounded_values

waveforms

Classes:

  • Blackman

    A Blackman window of a specified duration and area under the curve.

  • Constant

    A constant waveform over a given duration.

  • Delay

    An empty waveform.

  • Interpolated

    A waveform created from interpolation of a set of data points.

  • PiecewiseLinear

    A piecewise linear waveform.

  • Ramp

    A ramp that linearly interpolates between an initial and final value.

  • Sin

    An arbitrary sine over a given duration.

Blackman(duration: float, area: float)

A Blackman window of a specified duration and area under the curve.

Implements the Blackman window shaped waveform blackman(t) = A(0.42 - 0.5cos(αt) + 0.08cos(2αt)) A = area/(0.42duration) α = 2π/duration

See: https://en.wikipedia.org/wiki/Window_function#:~:text=Blackman%20window

Parameters:

  • duration
    (float) –

    The waveform duration.

  • area
    (float) –

    The integral of the waveform.

Example
blackman_wf = Blackman(100.0, area=3.14)

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(self, duration: float, area: float) -> None:
    """Initializes a new Blackman waveform."""
    super().__init__(duration, area=area)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

Constant(duration: float, value: float)

A constant waveform over a given duration.

Parameters:

  • duration
    (float) –

    the total duration.

  • value
    (float) –

    the value to take during the duration.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    value: float,
) -> None:
    super().__init__(duration, value=value)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

Delay(duration: float, *args: float, **kwargs: float | np.ndarray)

An empty waveform.

Parameters:

  • duration
    (float) –

    the total duration of the waveform.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/base_waveforms.py
def __init__(
    self,
    duration: float,
    *args: float,
    **kwargs: float | np.ndarray,
) -> None:
    """Initializes the Waveform.

    Arguments:
        duration: the total duration of the waveform.
    """

    if duration <= 0:
        raise ValueError("Duration needs to be a positive non-zero value.")

    if len(args) > 0:
        raise ValueError(
            f"Extra arguments in {type(self).__name__} need to be passed as keyword arguments"
        )

    self._duration = duration
    self._params_dict = kwargs

    self._max: float | None = None
    self._min: float | None = None

    for key, value in kwargs.items():
        setattr(self, key, value)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

Interpolated(duration: float, values: ArrayLike, times: Optional[ArrayLike] = None, interpolator: str = 'PchipInterpolator', **interpolator_kwargs: Any)

A waveform created from interpolation of a set of data points.

Parameters:

  • duration
    (int) –

    The waveform duration (in ns).

  • values
    (ArrayLike) –

    Values of the interpolation points. Must be a list of castable to float or a parametrized object.

  • times
    (ArrayLike, default: None ) –

    Fractions of the total duration (between 0 and 1), indicating where to place each value on the time axis. Must be a list of castable to float or a parametrized object. If not given, the values are spread evenly throughout the full duration of the waveform.

  • interpolator
    (str, default: 'PchipInterpolator' ) –

    The SciPy interpolation class to use. Supports "PchipInterpolator" and "interp1d".

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    values: ArrayLike,
    times: Optional[ArrayLike] = None,
    interpolator: str = "PchipInterpolator",
    **interpolator_kwargs: Any,
):
    """Initializes a new Interpolated waveform."""
    super().__init__(duration)
    self._values = np.array(values, dtype=float)
    if times:  # fractional times in [0,1]
        if any([(ft < 0) or (ft > 1) for ft in times]):
            raise ValueError("All values in `times` must be in [0,1].")
        self._times = np.array(times, dtype=float)
        if len(times) != len(self._values):
            raise ValueError(
                "Arguments `values` and `times` must be arrays of the same length."
            )
    else:
        self._times = np.linspace(0, 1, num=len(self._values))

    if interpolator not in self._valid_interpolators:
        raise ValueError(
            f"Invalid interpolator '{interpolator}', only "
            "accepts: " + ", ".join(self._valid_interpolators)
        )
    self._interpolator = interpolator
    self._interpolator_kwargs = interpolator_kwargs

    interp_cls = getattr(interpolate, interpolator)
    self._interp_func = interp_cls(duration * self._times, self._values, **interpolator_kwargs)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

PiecewiseLinear(durations: list | tuple, values: list | tuple)

A piecewise linear waveform.

Creates a composite waveform of N ramps that linearly interpolate through the given N+1 values.

Parameters:

  • durations
    (list | tuple) –

    list or tuple of N duration values.

  • values
    (list | tuple) –

    list or tuple of N+1 waveform values.

Methods:

  • function

    Identifies the right waveform in the composition and evaluates it at time t.

  • max

    Get the maximum value of the waveform.

  • min

    Get the approximate minimum value of the waveform.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • durations (list[float]) –

    Returns the list of durations of each individual waveform.

  • n_waveforms (int) –

    Returns the number of waveforms.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

  • times (list[float]) –

    Returns the list of times when each individual waveform starts.

  • waveforms (list[Waveform]) –

    Returns a list of the individual waveforms.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    durations: list | tuple,
    values: list | tuple,
) -> None:
    if not (isinstance(durations, (list, tuple)) or isinstance(values, (list, tuple))):
        raise TypeError(
            "A PiecewiseLinear waveform requires a list or tuple of durations and values."
        )

    if len(durations) + 1 != len(values) or len(durations) == 1:
        raise ValueError(
            "A PiecewiseLinear waveform requires N durations and N + 1 values, for N >= 2."
        )

    for duration in durations:
        if duration == 0.0:
            raise ValueError("A PiecewiseLinear interval cannot have zero duration.")

    self.values = values

    wfs = [Ramp(dur, values[i], values[i + 1]) for i, dur in enumerate(durations)]

    super().__init__(*wfs)
duration: float property

Returns the duration of the waveform.

durations: list[float] property

Returns the list of durations of each individual waveform.

n_waveforms: int property

Returns the number of waveforms.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

times: list[float] property

Returns the list of times when each individual waveform starts.

waveforms: list[Waveform] property

Returns a list of the individual waveforms.

function(t: float) -> float

Identifies the right waveform in the composition and evaluates it at time t.

Source code in qoolqit/waveforms/base_waveforms.py
def function(self, t: float) -> float:
    """Identifies the right waveform in the composition and evaluates it at time t."""
    idx = np.searchsorted(self.times, t, side="right") - 1
    if idx == -1:
        return 0.0
    if idx == self.n_waveforms:
        if t == self.times[-1]:
            idx = idx - 1
        else:
            return 0.0

    local_t = t - self.times[idx]
    value: float = self.waveforms[idx](local_t)
    return value
max() -> float

Get the maximum value of the waveform.

Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float:
    """Get the maximum value of the waveform."""
    return max([wf.max() for wf in self.waveforms])
min() -> float

Get the approximate minimum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float:
    """Get the approximate minimum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the minimum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._min is None:
        self._approximate_min_max()
    return cast(float, self._min)
Ramp(duration: float, initial_value: float, final_value: float)

A ramp that linearly interpolates between an initial and final value.

Parameters:

  • duration
    (float) –

    the total duration.

  • initial_value
    (float) –

    the initial value at t = 0.

  • final_value
    (float) –

    the final value at t = duration.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    initial_value: float,
    final_value: float,
) -> None:
    super().__init__(duration, initial_value=initial_value, final_value=final_value)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

Sin(duration: float, amplitude: float = 1.0, omega: float = 1.0, phi: float = 0.0, shift: float = 0.0)

An arbitrary sine over a given duration.

Parameters:

  • duration
    (float) –

    the total duration.

  • amplitude
    (float, default: 1.0 ) –

    the amplitude of the sine wave.

  • omega
    (float, default: 1.0 ) –

    the frequency of the sine wave.

  • phi
    (float, default: 0.0 ) –

    the phase of the sine wave.

  • shift
    (float, default: 0.0 ) –

    the vertical shift of the sine wave.

Methods:

  • max

    Get the approximate maximum value of the waveform.

  • min

    Get the approximate minimum value of the waveform.

Attributes:

  • duration (float) –

    Returns the duration of the waveform.

  • params (dict[str, float | ndarray]) –

    Dictionary of parameters used by the waveform.

Source code in qoolqit/waveforms/waveforms.py
def __init__(
    self,
    duration: float,
    amplitude: float = 1.0,
    omega: float = 1.0,
    phi: float = 0.0,
    shift: float = 0.0,
) -> None:
    super().__init__(duration, amplitude=amplitude, omega=omega, phi=phi, shift=shift)
duration: float property

Returns the duration of the waveform.

params: dict[str, float | np.ndarray] property

Dictionary of parameters used by the waveform.

max() -> float

Get the approximate maximum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the maximum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def max(self) -> float:
    """Get the approximate maximum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the maximum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._max is None:
        self._approximate_min_max()
    return cast(float, self._max)
min() -> float

Get the approximate minimum value of the waveform.

This is a brute-force method that samples the waveform over a pre-defined number of points to find the minimum value in the duration. Custom waveforms that have an easy to compute maximum value should override this method.

Source code in qoolqit/waveforms/base_waveforms.py
def min(self) -> float:
    """Get the approximate minimum value of the waveform.

    This is a brute-force method that samples the waveform over a
    pre-defined number of points to find the minimum value in the
    duration. Custom waveforms that have an easy to compute
    maximum value should override this method.
    """
    if self._min is None:
        self._approximate_min_max()
    return cast(float, self._min)