Skip to content

qubosolver.solving.hybrid

qubosolver.solving.hybrid

Hybrid quantum-classical solvers combining Pasqal devices with classical optimization.

Modules:

  • drive_bayesian_search –

    Hybrid quantum-classical QUBO solver using Bayesian optimization of drive schedules.

Hybrid quantum-classical QUBO solver using Bayesian optimization of drive schedules.

Runs a Bayesian search (via skopt.gp_minimize) over analog drive waveform parameters, executing a quantum simulation at each evaluation and minimizing a configurable objective of the resulting solution. Can be used as a standalone hybrid solving algorithm, or as a drive-shaping method to produce a tuned qoolqit.Drive for another solver.

Classes:

  • Config –

    Configuration for the Bayesian-optimization hybrid solver / drive shaper.

Functions:

  • solve –

    Solve a QUBO instance via Bayesian optimization of a drive schedule.

Config dataclass

Config(initial_amplitude_knots: list[float] = (lambda: [0.5, 0.9, 0.5])(), initial_detuning_knots: list[float] = (lambda: [-0.8, 0.0, 0.8])(), n_evaluations: int = 20, seed: int | None = None, objective_fn: Callable[[Solution], float] = _default_objective, default_sequence_duration: int = 50000)

Configuration for the Bayesian-optimization hybrid solver / drive shaper.

Attributes:

  • initial_amplitude_knots (list[float]) –

    Initial guess for the amplitude waveform's three interior knots, each normalized in [0, 1].

  • initial_detuning_knots (list[float]) –

    Initial guess for the detuning waveform's three knots, each normalized in [-1, 1].

  • n_evaluations (int) –

    Number of Bayesian optimization evaluations.

  • seed (int | None) –

    Random seed for reproducibility.

  • objective_fn (Callable[[Solution], float]) –

    Function to minimize: takes a Solution and returns a number. Defaults to the minimum cost among the sampled bitstrings; override to minimize something else, e.g. the average cost.

  • default_sequence_duration (int) –

    Fallback maximum sequence duration (ns) injected when the target device has no max_duration cap.

solve

solve(instance: Instance, register: qoolqit.Register, *, backend: protocols.Backend, device: qoolqit.Device, dmm: bool = True, config: Config | None = None) -> tuple[Solution, qoolqit.Drive]

Solve a QUBO instance via Bayesian optimization of a drive schedule.

Uses skopt.gp_minimize to search over waveform parameters, running a quantum simulation at each evaluation and minimizing config.objective_fn of the resulting Solution. This is a hybrid quantum-classical solving algorithm in its own right, and its returned drive can also be reused as the output of a drive-shaping step for another solver.

Parameters:

  • instance (Instance) –

    The QUBO Instance to solve.

  • register (qoolqit.Register) –

    The physical atom register.

  • backend (protocols.Backend) –

    Execution backend for running simulations during optimization.

  • device (qoolqit.Device) –

    Target quantum device.

  • dmm (bool, default: True ) –

    Whether to use the Detuning Map Modulator.

  • config (Config | None, default: None ) –

    Optimization parameters, including the initial waveform knots, number of evaluations, and objective function.

Returns:

Source code in qubosolver/solving/hybrid/drive_bayesian_search.py
def solve(
    instance: Instance,
    register: qoolqit.Register,
    *,
    backend: protocols.Backend,
    device: qoolqit.Device,
    dmm: bool = True,
    config: Config | None = None,
) -> tuple[Solution, qoolqit.Drive]:
    """Solve a QUBO instance via Bayesian optimization of a drive schedule.

    Uses [`skopt.gp_minimize`](https://scikit-optimize.github.io/stable/modules/generated/skopt.gp_minimize.html)
    to search over waveform parameters, running a
    quantum simulation at each evaluation and minimizing [`config.objective_fn`][Config]
    of the resulting [`Solution`][]. This is a hybrid quantum-classical solving
    algorithm in its own right, and its returned drive can also be reused as
    the output of a drive-shaping step for another solver.

    Args:
        instance: The QUBO [`Instance`][] to solve.
        register: The physical atom register.
        backend: Execution backend for running simulations during optimization.
        device: Target quantum device.
        dmm: Whether to use the Detuning Map Modulator.
        config: Optimization parameters, including the initial waveform knots,
            number of evaluations, and objective function.

    Returns:
        A tuple of the best [`qoolqit.Drive`][] found and the corresponding
            [`Solution`][].
    """
    config = config or Config()

    if dmm and not support_dmm(device):
        logging.warning(
            "dmm=True was requested but device %r does not support a DMM channel; "
            "falling back to a global detuning drive.",
            device,
        )
        dmm = False

    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)]

    initial_params = config.initial_amplitude_knots + config.initial_detuning_knots

    def run(x: list[float], eval: bool = True) -> tuple[float, Solution, qoolqit.Drive]:

        solution = Solution()
        drive = _build_drive(
            instance,
            x,
            dmm=dmm,
            device=device,
            register=register,
        )

        try:
            solution = _run_simulation(
                instance.matrix,
                register,
                drive,
                device,
                backend,
                config,
            )
            if eval:
                cost_eval = config.objective_fn(solution)
                if not np.isfinite(cost_eval):
                    print(f"[Warning] Non-finite cost encountered: {cost_eval} at x={x}")
                    cost_eval = 1e4
            else:
                cost_eval = float("nan")

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

    def objective(x: list[float]) -> float:
        cost_eval, _, _ = run(x)
        config._callback_fn(_CallbackInfo(x=x, cost_eval=cost_eval))

        return cost_eval

    opt_result = gp_minimize(
        objective,
        bounds,
        x0=initial_params,
        n_calls=config.n_evaluations,
        random_state=config.seed,
    )

    best_params = opt_result.x if opt_result else initial_params
    _, solution, drive = run(best_params, eval=False)

    return solution, drive