Skip to content
Pasqal Documentation

Pulse Shaping Methods

Quantum devices can be programmed by specifying a sequence of pulses. The pulse shaping configuration part (the pulse_shaping field of SolverConfig) defines how the pulse parameters are constructed.

In this notebook, we show how to use different pulse shaping methods.

Here, the two available pulse shaping methods are shown:

  • ADIABATIC (Has no parameters to be customized).
  • OPTIMIZED (Has three parameters that can be customized).

We choose the method when defining the configurations, with pulse_shaping_method = PulseType.(method)

  • use_quantum: bool | None = False (for using the pulse shaping methods, we have to set it to True.)
  • backend: str | BackendType = BackendType.QUTIP (possibly can be replace by BackendType.EMU_MPS or any value in BackendType)
  • device: str | DeviceType | None = DeviceType.DIGITAL_ANALOG_DEVICE (also available: ANALOG_DEVICE)
  • embedding_method: str | EmbedderType | None = EmbedderType.GREEDY (also available: BLADE)
  • pulse_shaping_method: str | PulseType | None = PulseType.ADIABATIC (also available: OPTIMIZED)
  • re_execute_opt_pulse: bool = False (True) Whether we take the last pulse and make another optimization round following the pipeline (execute) or just take the results of the last one
  • n_calls: Number of optimization rounds; default is 20 and minimum is 12.
  • initial_omega_parameters: [5.0, 10.0, 5.0,] List with initial values for Amplitude (5.0, 10, 5.0) when using Optimized Pulse
  • initial_detuning_parameters: [-10.0, 0.0, 10.0] List with initial values for Detuning (-10.0, 0.0, 10.0) when using Optimized Pulse
import torch
from qubosolver.qubo_instance import QUBOInstance
from qubosolver.config import SolverConfig, PulseShapingConfig
from qubosolver.qubo_types import PulseType
from qubosolver.solver import QuboSolver

Load the instance as a QUBOInstance object

Section titled “Load the instance as a QUBOInstance object”

Here, we have a 3x3 QUBO matrix with negative diagonal and positive off-diagonal terms.

coefficients = torch.tensor([[-1.0, 0.5, 0.2], [0.5, -2.0, 0.3], [0.2, 0.3, -3.0]])
instance = QUBOInstance(coefficients)

Default method

default_config = SolverConfig.from_kwargs(
use_quantum=True, pulse_shaping=PulseShapingConfig(pulse_shaping_method=PulseType.ADIABATIC),
)
solver = QuboSolver(instance, default_config)
solution = solver.solve()
print(solution)

For the OPTIMIZED pulse shaping, we have the following parameters:

  • n_calls
  • re_execute_opt_pulse
  • initial_omega_parameters
  • initial_detuning_parameters
default_config = SolverConfig.from_kwargs(
use_quantum=True, pulse_shaping=PulseShapingConfig(pulse_shaping_method=PulseType.OPTIMIZED),
)
solver = QuboSolver(instance, default_config)
solution = solver.solve()
print(solution)
default_config = SolverConfig.from_kwargs(
use_quantum=True, pulse_shaping=PulseShapingConfig(pulse_shaping_method=PulseType.OPTIMIZED), n_calls=13
)
solver = QuboSolver(instance, default_config)
solution = solver.solve()
print(solution)

Changing initial_omega_parameters and initial_detuning_parameters

Section titled “Changing initial_omega_parameters and initial_detuning_parameters”
default_config = SolverConfig.from_kwargs(
use_quantum=True, pulse_shaping=PulseShapingConfig(pulse_shaping_method=PulseType.OPTIMIZED, initial_omega_parameters=[2.0, 15.0, 5.0,], initial_detuning_parameters=[-45.0, 0.0, 25.0]),
)
solver = QuboSolver(instance, default_config)
solution = solver.solve()
print(solution)
default_config = SolverConfig.from_kwargs(
use_quantum=True, pulse_shaping=PulseShapingConfig(pulse_shaping_method=PulseType.OPTIMIZED, re_execute_opt_pulse=True),
)
solver = QuboSolver(instance, default_config)
solution = solver.solve()
print(solution)

One can change the pulse shaping method by incorporating custom functions for:

  • Evaluating a candidate bitstring and QUBO via custom_qubo_cost
  • Performing optimization with a different objective than the best cost via custom_objective
  • Adding callback functions via callback_objective.
from qubosolver.utils.qubo_eval import calculate_qubo_cost
# example of penalization
def penalized_qubo(bitstring: str, QUBO: torch.Tensor) -> float:
return calculate_qubo_cost(bitstring, QUBO) + 2 * bitstring.count("0")
# example of saving intermediate results
opt_results = list()
def callback(d: dict) -> None:
opt_results.append(d)
# example of using an average cost
def average_ojective(
bitstrings: list,
counts: list,
probabilities: list,
costs: list,
best_cost: float,
best_bitstring: str,
) -> float:
return sum([p * c for p, c in zip(probabilities, costs)])
pulse_shaping=PulseShapingConfig(pulse_shaping_method=PulseType.OPTIMIZED,
re_execute_opt_pulse=True,
custom_qubo_cost=penalized_qubo,
callback_objective=callback,
custom_objective = average_ojective,
)
config = SolverConfig(
use_quantum=True,
pulse_shaping=pulse_shaping,
)
solver = QuboSolver(instance, config)
solution = solver.solve()
len(opt_results), opt_results[-1]
solution