Embedding workflow
Here is a breakdown of the current workflow implementation for the embedding of a QUBO matrix, using several embedding methods and parameters.
Using default configuration
The SolverConfig()
without argument has a default behavior (e.g. Solver Config page) that allows for embedding using the minimum configuration according to the device.
Code example
import torch
from qubosolver.config import SolverConfig, EmbeddingConfig, BackendConfig
from qubosolver.solver import QUBOInstance, QuboSolver
# define qubo matrix
coefficients = torch.tensor([[0, 1, 2], [1, 0, 3], [2, 3, 0]])
# Instantiate a QUBOInstance with coefficients
instance = QUBOInstance(coefficients)
# define the solver with default configuration
default_config = SolverConfig()
solver = QuboSolver(instance, default_config)
geometry = solver.embedding()
# draw the register
# geometry.register.draw()
BLaDE config
The following configuration uses the BLaDE method with specific dimension layers and a number of steps per round (i.e. the number of iterations per layer). Starting positions are implicitely defined but it can be set here, as long as it matches the first dimension layer.
embedconfig = EmbeddingConfig(embedding_method="blade", blade_dimensions=[5, 4, 3, 2], blade_steps_per_round=300)
blade_config = SolverConfig(
use_quantum=True,
embedding=embedconfig,
)
solver = QuboSolver(instance, blade_config)
geometry = solver.embedding()
# geometry.register.draw()
Greedy embedder config
The following uses the greedy embedding method on a triangular lattice layout with a number of traps equals to the size of the QUBO. It also allows working on a square lattice layout, as well as increasing the number of traps according to the device specifications.
from qoolqit._solvers.types import DeviceType
embedconfig = EmbeddingConfig(embedding_method="greedy", traps=instance.size, layout_greedy_embedder="triangular",)
backend = BackendConfig(device=DeviceType.ANALOG_DEVICE)
greedy_config = SolverConfig(
use_quantum=True,
embedding=embedconfig,
backend_config = backend,
)
solver = QuboSolver(instance, greedy_config)
geometry = solver.embedding()
# geometry.register.draw()
Custom embedder config
If one desires to develop his own embedding method, a subclass of qubosolver.pipeline.embedder.BaseEmbedder
should be implemented with a mandatory embed
method.
The embed
method def embed(self) -> qubosolver.pipeline.targets.Register
specify how the problem is mapped into a register of qubits when running using a quantum device. Let us show a simple example where each variable \(i\) is mapped into a qubit lying on a horizontal line (with coordinates \([i, 0]\)).
import typing
from qubosolver.pipeline.embedder import BaseEmbedder
from qubosolver.pipeline.targets import Register as TargetRegister
from qubosolver.config import (
EmbeddingConfig,
SolverConfig,
)
from pulser.register import Register as PulserRegister
class FixedEmbedder(BaseEmbedder):
@typing.no_type_check
def embed(self) -> TargetRegister:
qubits = {f"q{i}": (i,0) for i in range(self.instance.coefficients.shape[0])}
register = PulserRegister(qubits)
return TargetRegister(self.config.backend_config.device, register)
config = SolverConfig(
use_quantum=True,
embedding=EmbeddingConfig(embedding_method=FixedEmbedder),
)