Skip to content

Quantum-related components of a QUBO solver

Drive Shapping

BaseDriveShaper(instance, config, backend)

Bases: ABC

Abstract base class for generating Qoolqit drives based on a QUBO problem.

This class transforms the structure of a QUBOInstance into a quantum waveform sequence or drive that can be applied to a physical register. The register is passed at the time of drive generation, not during initialization.

ATTRIBUTE DESCRIPTION
instance

The QUBO problem instance.

TYPE: QUBOInstance

config

The solver configuration.

TYPE: SolverConfig

drive

A saved current drive obtained by generate.

TYPE: Drive

backend

Backend to use.

TYPE: Backend

device

Device from backend.

TYPE: Device

Initialize the drive shaping module with a QUBO instance.

PARAMETER DESCRIPTION
instance

The QUBO problem instance.

TYPE: QUBOInstance

config

The solver configuration.

TYPE: SolverConfig

backend

Backend to use.

TYPE: Backend

Source code in qubosolver/pipeline/drive.py
def __init__(self, instance: QUBOInstance, config: SolverConfig, backend: concepts.Backend):
    """
    Initialize the drive shaping module with a QUBO instance.

    Args:
        instance (QUBOInstance): The QUBO problem instance.
        config (SolverConfig): The solver configuration.
        backend (Backend): Backend to use.
    """
    self.instance: QUBOInstance = instance
    self.config: SolverConfig = config
    self.drive: Drive | None = None
    self.backend = backend
    self.device = self.config.device

    # check if device allow DMM
    self.dmm = self.config.drive_shaping.dmm and (
        len(list(self.config.device._device.dmm_channels.keys())) > 0
    )

generate(register) abstractmethod

Generate a drive based on the problem and the provided register.

PARAMETER DESCRIPTION
register

The physical register layout.

TYPE: Register

RETURNS DESCRIPTION
Drive

A generated Drive.

TYPE: Drive

QUBOSolution

An instance of the qubo solution

TYPE: QUBOSolution

Source code in qubosolver/pipeline/drive.py
@abstractmethod
def generate(
    self,
    register: Register,
) -> tuple[Drive, QUBOSolution]:
    """
    Generate a drive based on the problem and the provided register.

    Args:
        register (Register): The physical register layout.

    Returns:
        Drive: A generated Drive.
        QUBOSolution: An instance of the qubo solution
    """
    pass

HeuristicDriveShaper(instance, config, backend)

Bases: BaseDriveShaper

Heuristic schedule drive shaper.

With DMM

Final target encoding: d_i = -0.5 * Q_ii

DMM convention in this stack: WeightedDetuning waveform must be <= 0

Hence we encode the local final detuning as: delta_i(T) = delta_g(T) + delta_dmm(T) * w_i

with: delta_g(T) = d_max delta_dmm(T) = -(d_max - d_min) <= 0 w_i = (d_max - d_i) / (d_max - d_min) in [0, 1]

so that: delta_i(T) = d_i

Without DMM

Only a global detuning is available, so the final detuning is chosen as: delta_g(T) = mean(d_i) and no weighted detunings are declared.

Source code in qubosolver/pipeline/drive.py
def __init__(self, instance: QUBOInstance, config: SolverConfig, backend: concepts.Backend):
    """
    Initialize the drive shaping module with a QUBO instance.

    Args:
        instance (QUBOInstance): The QUBO problem instance.
        config (SolverConfig): The solver configuration.
        backend (Backend): Backend to use.
    """
    self.instance: QUBOInstance = instance
    self.config: SolverConfig = config
    self.drive: Drive | None = None
    self.backend = backend
    self.device = self.config.device

    # check if device allow DMM
    self.dmm = self.config.drive_shaping.dmm and (
        len(list(self.config.device._device.dmm_channels.keys())) > 0
    )

OptimizedDriveShaper(instance, config, backend)

Bases: BaseDriveShaper

Drive shaper that uses optimization to find the best drive parameters for solving QUBOs. Returns an optimized drive, the bitstrings, their counts, probabilities, and costs.

ATTRIBUTE DESCRIPTION
drive

current drive.

TYPE: Drive

best_cost

Current best cost.

TYPE: float

best_bitstring

Current best bitstring.

TYPE: Tensor | list

bitstrings

List of current bitstrings obtained.

TYPE: Tensor | list

counts

Frequencies of bitstrings.

TYPE: Tensor | list

probabilities

Probabilities of bitstrings.

TYPE: Tensor | list

costs

Qubo cost.

TYPE: Tensor | list

optimized_custom_qubo_cost

Apply a different qubo cost evaluation during optimization. Must be defined as: def optimized_custom_qubo_cost(bitstring: str, QUBO: torch.Tensor) -> float. Defaults to None, meaning we use the default QUBO evaluation.

TYPE: Callable[[str, Tensor], float]

optimized_custom_objective_fn

For bayesian optimization, one can change the output of self.run_simulation to optimize differently. Instead of using the best cost out of the samples, one can change the objective for an average, or any function out of the form cost_eval = optimized_custom_objective_fn(bitstrings, counts, probabilities, costs, best_cost, best_bitstring) Defaults to None, which means we optimize using the best cost out of the samples.

TYPE: Callable[[list, list, list, list, float, str], float]

optimized_callback_objective

Apply a callback during bayesian optimization. Only accepts one input dictionary created during optimization d = {"x": x, "cost_eval": cost_eval} hence should be defined as: def callback_fn(d: dict) -> None: Defaults to None, which means no callback is applied.

TYPE: Callable[..., None]

Instantiate an OptimizedDriveShaper.

PARAMETER DESCRIPTION
instance

Qubo instance.

TYPE: QUBOInstance

config

Configuration for solving.

TYPE: SolverConfig

backend

Backend to use during optimization.

TYPE: Backend

Source code in qubosolver/pipeline/drive.py
def __init__(
    self,
    instance: QUBOInstance,
    config: SolverConfig,
    backend: concepts.Backend,
):
    """Instantiate an `OptimizedDriveShaper`.

    Args:
        instance (QUBOInstance): Qubo instance.
        config (SolverConfig): Configuration for solving.
        backend (Backend): Backend to use during optimization.

    """
    super().__init__(instance, config, backend)

    self.drive = None
    self.best_cost = None
    self.best_bitstring = None
    self.best_params = None
    self.bitstrings = None
    self.counts = None
    self.probabilities = None
    self.costs = None
    self.optimized_custom_qubo_cost = self.config.drive_shaping.optimized_custom_qubo_cost
    self.optimized_custom_objective_fn = self.config.drive_shaping.optimized_custom_objective
    self.optimized_callback_objective = self.config.drive_shaping.optimized_callback_objective

build_drive(params)

Build the drive waveform from a normalised parameter vector.

PARAMETER DESCRIPTION
params

6 values — 3 amplitude breakpoints then 3 detuning breakpoints, all normalised to [0, 1] (or [-1, 1] for detuning).

TYPE: list

RETURNS DESCRIPTION
Drive

Drive sequence.

TYPE: Drive

Source code in qubosolver/pipeline/drive.py
def build_drive(self, params: list) -> Drive:
    """Build the drive waveform from a normalised parameter vector.

    Args:
        params (list): 6 values — 3 amplitude breakpoints then 3 detuning
            breakpoints, all normalised to [0, 1] (or [-1, 1] for detuning).

    Returns:
        Drive: Drive sequence.
    """
    specs = self.device.specs
    max_seq_duration: float = specs["max_duration"] or 1e3
    max_amplitude: float = specs["max_amplitude"] or 1e4
    max_detuning: float = specs["max_abs_detuning"] or 1e4

    amp_params = [1e-9] + list(params[:3]) + [1e-9]
    det_params = list(params[3:])
    amp_params = [p * max_amplitude for p in amp_params]
    det_params = [p * max_detuning for p in det_params]

    amp_wave = InterpolatedWaveform(max_seq_duration, amp_params)
    det_wave = InterpolatedWaveform(max_seq_duration, det_params)

    dmm = None
    final_detuning = det_params[-1]
    if self.dmm and final_detuning > 0:
        dmm = constant_weighted_dmm(
            self.register,
            max_seq_duration,
            self.norm_weights_list,
            final_detuning=-final_detuning,
        )

    shaped_drive = Drive(amplitude=amp_wave, detuning=det_wave, dmm=dmm)

    return shaped_drive

compute_qubo_cost(bitstring, QUBO)

The qubo cost for a single bitstring to apply during optimization.

PARAMETER DESCRIPTION
bitstring

candidate bitstring.

TYPE: str

QUBO

qubo coefficients.

TYPE: Tensor

RETURNS DESCRIPTION
float

respective cost of bitstring.

TYPE: float

Source code in qubosolver/pipeline/drive.py
def compute_qubo_cost(self, bitstring: str, QUBO: torch.Tensor) -> float:
    """The qubo cost for a single bitstring to apply during optimization.

    Args:
        bitstring (str): candidate bitstring.
        QUBO (torch.Tensor): qubo coefficients.

    Returns:
        float: respective cost of bitstring.
    """
    if self.optimized_custom_qubo_cost is None:
        return calculate_qubo_cost(bitstring, QUBO)

    return cast(float, self.optimized_custom_qubo_cost(bitstring, QUBO))

generate(register)

Generate a drive via Bayesian optimisation.

PARAMETER DESCRIPTION
register

The physical register layout.

TYPE: Register

RETURNS DESCRIPTION
Drive

A generated Drive.

TYPE: Drive

QUBOSolution

An instance of the qubo solution

TYPE: QUBOSolution

Source code in qubosolver/pipeline/drive.py
def generate(
    self,
    register: Register,
) -> tuple[Drive, QUBOSolution]:
    """
    Generate a drive via Bayesian optimisation.

    Args:
        register (Register): The physical register layout.

    Returns:
        Drive: A generated Drive.
        QUBOSolution: An instance of the qubo solution
    """
    # TODO: Harmonize the output of the pulse_shaper generate
    QUBO = self.qubo_coefficients
    self.register = register

    self.norm_weights_list = self._compute_norm_weights()

    n_amp = 3
    n_det = 3

    eps = 0.0001
    zero = eps
    one = 1.0 - eps

    bounds = (
        [(zero, one)] * n_amp + [(-one, -zero)] + [(-one, one)] * (n_det - 2) + [(zero, one)]
    )
    x0 = (
        self.config.drive_shaping.optimized_initial_omega_parameters
        + self.config.drive_shaping.optimized_initial_detuning_parameters
    )

    def objective(x: list[float]) -> float:
        drive = self.build_drive(x)

        try:
            bitstrings, counts, probabilities, costs, cost_eval, best_bitstring = (
                self.run_simulation(
                    self.register,
                    drive,
                    QUBO,
                    convert_to_tensor=False,
                )
            )
            if self.optimized_custom_objective_fn is not None:
                cost_eval = self.optimized_custom_objective_fn(
                    bitstrings,
                    counts,
                    probabilities,
                    costs,
                    cost_eval,
                    best_bitstring,
                )
            if not np.isfinite(cost_eval):
                print(f"[Warning] Non-finite cost encountered: {cost_eval} at x={x}")
                cost_eval = 1e4

        except Exception as e:
            print(f"[Exception] Error during simulation at x={x}: {e}")
            cost_eval = 1e4

        if self.optimized_callback_objective is not None:
            self.optimized_callback_objective({"x": x, "cost_eval": cost_eval})
        return float(cost_eval)

    opt_result = gp_minimize(
        objective,
        bounds,
        x0=x0,
        n_calls=self.config.drive_shaping.optimized_n_calls,
        random_state=self.config.drive_shaping.optimized_seed,
    )

    if opt_result and opt_result.x:
        self.best_params = opt_result.x
        self.drive = self.build_drive(self.best_params)  # type: ignore[arg-type]

        (
            self.bitstrings,
            self.counts,
            self.probabilities,
            self.costs,
            self.best_cost,
            self.best_bitstring,
        ) = self.run_simulation(self.register, self.drive, QUBO, convert_to_tensor=True)

    if self.bitstrings is None or self.counts is None:
        # TODO: what needs to be returned here?
        # the generate function should always return a drive - even if it is not good.
        # we need to return a drive (self.drive) - which is none here.
        # return self.drive, QUBOSolution(None, None)
        raise RuntimeError("No solution found")

    assert self.costs is not None
    solution = QUBOSolution(
        bitstrings=self.bitstrings,
        counts=self.counts,
        probabilities=self.probabilities,
        costs=self.costs,
    )
    assert self.drive is not None
    return self.drive, solution

run_simulation(register, drive, QUBO, convert_to_tensor=True)

Run a quantum program using backend and returns a tuple of (bitstrings, counts, probabilities, costs, best cost, best bitstring).

PARAMETER DESCRIPTION
register

register of quantum program.

TYPE: Register

drive

drive to run on backend.

TYPE: Drive

QUBO

Qubo coefficients.

TYPE: Tensor

convert_to_tensor

Convert tuple components to tensors. Defaults to True.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
tuple

tuple of (bitstrings, counts, probabilities, costs, best cost, best bitstring)

TYPE: tuple

Source code in qubosolver/pipeline/drive.py
def run_simulation(
    self,
    register: Register,
    drive: Drive,
    QUBO: torch.Tensor,
    convert_to_tensor: bool = True,
) -> tuple:
    """Run a quantum program using backend and returns
        a tuple of (bitstrings, counts, probabilities, costs, best cost, best bitstring).

    Args:
        register (Register): register of quantum program.
        drive (Drive): drive to run on backend.
        QUBO (torch.Tensor): Qubo coefficients.
        convert_to_tensor (bool, optional): Convert tuple components to tensors.
            Defaults to True.

    Returns:
        tuple: tuple of (bitstrings, counts, probabilities, costs, best cost, best bitstring)
    """
    try:
        program = QuantumProgram(register=register, drive=drive)
        program.compile_to(
            self.device,
            profile=compiler_profile(self.config),
            device_max_duration_ratio=max_duration_ratio(self.config),
        )
        job = self.backend.run(program)
        bitstring_counts = job.results().final_bitstrings

        cost_dict = {b: self.compute_qubo_cost(b, QUBO) for b in bitstring_counts.keys()}

        best_bitstring = min(cost_dict, key=cost_dict.get)  # type: ignore[arg-type]
        best_cost = cost_dict[best_bitstring]

        if convert_to_tensor:
            keys = list(bitstring_counts.keys())
            values = list(bitstring_counts.values())

            bitstrings_tensor = torch.tensor(
                [[int(b) for b in bitstr] for bitstr in keys], dtype=torch.int32
            )
            counts_tensor = torch.tensor(values, dtype=torch.int32)
            probabilities_tensor = counts_tensor.float() / counts_tensor.sum()

            costs_tensor = torch.tensor(
                [self.compute_qubo_cost(b, QUBO) for b in keys], dtype=torch.float32
            )

            return (
                bitstrings_tensor,
                counts_tensor,
                probabilities_tensor,
                costs_tensor,
                best_cost,
                best_bitstring,
            )
        else:
            counts = list(bitstring_counts.values())
            nsamples = float(sum(counts))
            return (
                list(bitstring_counts.keys()),
                counts,
                [c / nsamples for c in counts],
                list(cost_dict.values()),
                best_cost,
                best_bitstring,
            )

    except Exception as e:
        print(f"Simulation failed: {e}")
        return (
            torch.tensor([]),
            torch.tensor([]),
            torch.tensor([]),
            torch.tensor([]),
            float("inf"),
            None,
        )

get_drive_shaper(instance, config, backend)

Method that returns the correct DriveShaper based on configuration. The correct drive shaping method can be identified using the config, and an object of this driveshaper can be returned using this function.

PARAMETER DESCRIPTION
instance

The QUBO problem to embed.

TYPE: QUBOInstance

config

The solver configuration used.

TYPE: SolverConfig

backend

Backend to extract device from or to use during drive shaping.

TYPE: Backend

RETURNS DESCRIPTION
BaseDriveShaper

The representative Drive Shaper object.

Source code in qubosolver/pipeline/drive.py
def get_drive_shaper(
    instance: QUBOInstance,
    config: SolverConfig,
    backend: concepts.Backend,
) -> BaseDriveShaper:
    """
    Method that returns the correct DriveShaper based on configuration.
    The correct drive shaping method can be identified using the config, and an
    object of this driveshaper can be returned using this function.

    Args:
        instance (QUBOInstance): The QUBO problem to embed.
        config (SolverConfig): The solver configuration used.
        backend (Backend): Backend to extract device from or to use
            during drive shaping.

    Returns:
        (BaseDriveShaper): The representative Drive Shaper object.
    """
    if config.drive_shaping.drive_shaping_method == DriveType.HEURISTIC:
        return HeuristicDriveShaper(instance, config, backend)
    elif config.drive_shaping.drive_shaping_method == DriveType.OPTIMIZED:
        return OptimizedDriveShaper(instance, config, backend)
    elif issubclass(config.drive_shaping.drive_shaping_method, BaseDriveShaper):
        return cast(
            BaseDriveShaper,
            config.drive_shaping.drive_shaping_method(instance, config, backend),
        )
    else:
        raise NotImplementedError

Embedding

BLaDEmbedder(instance, config, backend)

Bases: BaseEmbedder

Atom-register embedder using the qoolqit BLaDe (Block-Layout and Degree-based) matrix-embedding algorithm.

BLaDe jointly optimises atom positions to match the logical adjacency structure of the QUBO graph with the physical Rydberg interaction matrix. Configuration is taken from config.embedding (BLaDe-specific fields: blade_steps_per_round, blade_starting_positions, blade_dimensions, min_distance).

Source code in qubosolver/pipeline/embedder.py
def __init__(self, instance: QUBOInstance, config: SolverConfig, backend: concepts.Backend):
    """
    Args:
        instance (QUBOInstance): The QUBO problem to embed.
        config (SolverConfig): Solver configuration.
        backend (Backend): Execution backend providing device information.
    """
    self.instance: QUBOInstance = instance
    self.config: SolverConfig = config
    self.register: Register | None = None
    self.backend = backend

    # TODO: remove when bumping to qoolqit v1
    # for converting to qoolqit
    self._distance_conversion = self.config.device.converter.factors[2]

embed()

Run the BLaDe embedding algorithm and return the resulting register.

Reads embedding hyper-parameters from self.config.embedding, constructs a BladeConfig, runs Blade.embed on the QUBO coefficient matrix, optionally rescales coordinates to satisfy the min_distance constraint, and wraps the result as a Register. See Qoolqit's documentation for details.

RETURNS DESCRIPTION
Register

Atom register with positions optimised by BLaDe.

TYPE: Register

Source code in qubosolver/pipeline/embedder.py
def embed(self) -> Register:
    """Run the BLaDe embedding algorithm and return the resulting register.

    Reads embedding hyper-parameters from ``self.config.embedding``,
    constructs a ``BladeConfig``, runs ``Blade.embed`` on the QUBO
    coefficient matrix, optionally rescales coordinates to satisfy the
    ``min_distance`` constraint, and wraps the result as a ``Register``.
    See [Qoolqit's documentation](https://pasqal-io.github.io/qoolqit/latest/reference/embedding/#qoolqit.embedding.BladeConfig) for details.


    Returns:
        Register: Atom register with positions optimised by BLaDe.
    """
    embed_config = self.config.embedding
    default = BladeConfig()
    step_per_round = embed_config.blade_steps_per_round
    if step_per_round is None:
        step_per_round = default.steps_per_round
    if embed_config.blade_starting_positions is not None:
        starting_positions = embed_config.blade_starting_positions.numpy()
    else:
        starting_positions = None

    min_distance = self.config.embedding.min_distance
    max_radial_distance = self.config.device.specs["max_radial_distance"]
    if min_distance is None or max_radial_distance is None:
        device = self.config.device
        max_min_dist_ratio = None
    else:
        device = None
        max_min_dist_ratio = max_radial_distance / min_distance

    config = BladeConfig(
        steps_per_round=step_per_round,
        starting_positions=starting_positions,
        dimensions=tuple(embed_config.blade_dimensions),
        max_min_dist_ratio=max_min_dist_ratio,
        device=device,
    )

    _blade = Blade(config)
    graph = _blade.embed(self.instance.coefficients.numpy())
    if min_distance is not None:
        graph.rescale_coords(spacing=min_distance)
    register = Register.from_graph(graph)

    return register

BaseEmbedder(instance, config, backend)

Bases: ABC

Abstract base class for all embedders.

Prepares the geometry (register) of atoms based on the QUBO instance and returns a Register compatible with Pasqal/Pulser devices.

ATTRIBUTE DESCRIPTION
instance

The QUBO problem to embed.

TYPE: QUBOInstance

config

Solver configuration including embedding settings.

TYPE: SolverConfig

register

The generated register (set after embed).

TYPE: Register | None

backend

The execution backend (used to access device specs).

TYPE: Backend

Note

config.embedding.min_distance controls whether the generated register is rescaled after embedding, and its correct value depends on the drive-shaping method used:

  • Set to 1 + margin (e.g. 1.001) when pairing with drive shapers that support the MAX_ENERGY qoolqit compiler profile (e.g. HeuristicDriveShaper). The compiler may rescale atom coordinates at compile time; providing a value just above 1 (in normalised units) ensures the register satisfies the minimum-distance constraint while leaving room for the compiler to adjust it freely.

  • Set to None when pairing with drive shapers that do not use the MAX_ENERGY profile (e.g. OptimizedDriveShaper). In this case no rescaling is applied and the register coordinates are kept exactly as produced by the embedding algorithm, ready to be sent to the physical QPU as-is.

See Qoolqit's documentation for more details on rescaling:

PARAMETER DESCRIPTION
instance

The QUBO problem to embed.

TYPE: QUBOInstance

config

Solver configuration.

TYPE: SolverConfig

backend

Execution backend providing device information.

TYPE: Backend

Source code in qubosolver/pipeline/embedder.py
def __init__(self, instance: QUBOInstance, config: SolverConfig, backend: concepts.Backend):
    """
    Args:
        instance (QUBOInstance): The QUBO problem to embed.
        config (SolverConfig): Solver configuration.
        backend (Backend): Execution backend providing device information.
    """
    self.instance: QUBOInstance = instance
    self.config: SolverConfig = config
    self.register: Register | None = None
    self.backend = backend

    # TODO: remove when bumping to qoolqit v1
    # for converting to qoolqit
    self._distance_conversion = self.config.device.converter.factors[2]

embed() abstractmethod

Create a register (atom layout) for the QUBO instance.

RETURNS DESCRIPTION
Register

The register.

TYPE: Register

Source code in qubosolver/pipeline/embedder.py
@abstractmethod
def embed(self) -> Register:
    """Create a register (atom layout) for the QUBO instance.

    Returns:
        Register: The register.
    """
    ...

GreedyEmbedder(instance, config, backend)

Bases: BaseEmbedder

Create an embedding in a greedy fashion.

At each step, place one logical node onto one trap to minimize the incremental mismatch between the logical QUBO matrix Q and the physical interaction matrix U (approx. C / ||r_i - r_j||^6).

Source code in qubosolver/pipeline/embedder.py
def __init__(self, instance: QUBOInstance, config: SolverConfig, backend: concepts.Backend):
    """
    Args:
        instance (QUBOInstance): The QUBO problem to embed.
        config (SolverConfig): Solver configuration.
        backend (Backend): Execution backend providing device information.
    """
    self.instance: QUBOInstance = instance
    self.config: SolverConfig = config
    self.register: Register | None = None
    self.backend = backend

    # TODO: remove when bumping to qoolqit v1
    # for converting to qoolqit
    self._distance_conversion = self.config.device.converter.factors[2]

embed()

Creates a layout of atoms as the register.

RETURNS DESCRIPTION
Register

The register.

TYPE: Register

Source code in qubosolver/pipeline/embedder.py
@typing.no_type_check
def embed(self) -> Register:
    """
    Creates a layout of atoms as the register.

    Returns:
        Register: The register.
    """
    if self.config.embedding.greedy_traps == -1:
        self.config.embedding.greedy_traps = self._number_of_traps_from_device(
            self.config.device
        )

    if self.config.embedding.greedy_traps < self.instance.size:
        raise ValueError(
            "Number of traps must be at least equal to the number of atoms on the register."
        )

    # compute density (unchanged)
    self.config.embedding.greedy_density = calculate_density(
        self.instance.coefficients, self.instance.size
    )

    # build params for the Greedy algorithm
    params = {
        "device": self.config.device._device,
        "layout": self.config.embedding.greedy_layout,
        "traps": int(self.config.embedding.greedy_traps),
        "spacing": float(self.config.embedding.greedy_spacing),
        # animation controls (all read by Greedy)
        "draw_steps": bool(self.config.embedding.draw_steps),  # collect per-step data
        "animation": bool(self.config.embedding.draw_steps),  # render animation after run
        "animation_save_path": self.config.embedding.animation_save_path,  # optional export
    }

    # --- DEBUG / INFO: show where Greedy comes from + the params we’ll pass
    dev = params["device"]
    dev_str = (
        getattr(dev, "name", None)
        or getattr(dev, "device_name", None)
        or dev.__class__.__name__
    )
    printable = dict(params)
    printable["device"] = dev_str  # avoid dumping the whole object
    # --- Call Greedy (unchanged public signature)
    best, _, coords, _, _ = Greedy().launch_greedy(
        Q=self.instance.coefficients,
        params=params,
        # no extra kwargs; Greedy reads animation/draw/save_path from params
    )
    min_distance = self.config.embedding.min_distance
    if min_distance is not None:
        min_reg_distance = torch.cdist(coords, coords).fill_diagonal_(float("inf")).min()
        coords *= min_distance / min_reg_distance
    else:
        coords /= self._distance_conversion

    # build the register (unchanged)
    qubits = {f"q{i}": coord for i, coord in enumerate(coords)}
    register = Register(qubits)
    return register

get_embedder(instance, config, backend)

Method that returns the correct embedder based on configuration. The correct embedding method can be identified using the config, and an object of this embedding can be returned using this function.

PARAMETER DESCRIPTION
instance

The QUBO problem to embed.

TYPE: QUBOInstance

config

The quantum device to target.

TYPE: Device

RETURNS DESCRIPTION
BaseEmbedder

The representative embedder object.

Source code in qubosolver/pipeline/embedder.py
def get_embedder(
    instance: QUBOInstance, config: SolverConfig, backend: concepts.Backend
) -> BaseEmbedder:
    """
    Method that returns the correct embedder based on configuration.
    The correct embedding method can be identified using the config, and an
    object of this embedding can be returned using this function.

    Args:
        instance (QUBOInstance): The QUBO problem to embed.
        config (Device): The quantum device to target.

    Returns:
        (BaseEmbedder): The representative embedder object.
    """

    if config.embedding.embedding_method == EmbedderType.BLADE:
        return BLaDEmbedder(instance, config, backend)
    elif config.embedding.embedding_method == EmbedderType.GREEDY:
        return GreedyEmbedder(instance, config, backend)
    elif issubclass(config.embedding.embedding_method, BaseEmbedder):
        return typing.cast(
            BaseEmbedder, config.embedding.embedding_method(instance, config, backend)
        )
    else:
        raise NotImplementedError