Tutorial 5 - Advanced quantum programming
Note: This tutorial is designed for users who already understand quantum programming using the pulser
library. For more details on this library, see its tutorial (external).
By design, a quantum solver is composed of two main quantum components for solving a problem:
- an embedder, i.e. a mechanism used to customize the layout of neutral atoms on the quantum device.
- a pulse shaper, i.e. a mechanism used to customize the laser pulse to which the neutral atoms are subjected during the execution of the quantum algorithm.
These can be specified in the SolverConfig
. Most users can use the default embedders and pulse shapers provided with this library. However, if you wish to alter the behavior of the solver, perhaps for the sake of research, debugging or learning, you can implement your own embedders or pulse shapers. This tutorial dives into currently available components, as well as designing custom ones.
Embedder
Section titled “Embedder”Default
Section titled “Default”When instantiating a SolverConfig
, a default embedder is already made available. To access the resulting embedding from a solver, simply call the embedding
method as follows:
from mis import MISSolver, MISInstance, SolverConfig, BackendConfig, BackendType
from networkx import erdos_renyi_graph
# User can fix the seed for reproducibilityseed = 0graph = erdos_renyi_graph(n=25, p=0.4, seed=seed)instance = MISInstance(graph)
qutip_config = BackendConfig( backend = BackendType.QUTIP)
config = SolverConfig(backend = qutip_config)solver = MISSolver(instance, config)
geometry = solver.embedding()
# draw the register# geometry.draw()
Custom
Section titled “Custom”To design our own embedding method, we need to define a class inhereting from mis.pipeline.embedder.BaseEmbedder
and implement an embed
method as follows:
from mis.pipeline.embedder import BaseEmbedderfrom pulser import Registerfrom qoolqit._solvers.backends import BaseBackendfrom mis.pipeline.layout import Layout
class DefaultEmbedder(BaseEmbedder): """ The DefaultEmbedder class available in mis. """
def embed(self, instance: MISInstance, config: SolverConfig, backend: BaseBackend) -> Register: device = backend.device()
# Use Layout helper to get rescaled coordinates and interaction graph layout = Layout.from_device(data=instance, device=device)
# Finally, prepare register. return Register( qubits={f"q{node}": pos for (node, pos) in layout.coords.items()} )
custom_config = SolverConfig(backend = qutip_config, embedder=DefaultEmbedder())
custom_solver = MISSolver(instance, custom_config)default_geometry = custom_solver.embedding()
# draw the register# default_geometry.draw()
Pulse shaper
Section titled “Pulse shaper”Default
Section titled “Default”When instantiating a SolverConfig
, a default pulse shaper is already made available. To access the resulting embedding from a solver, simply call the pulse
method as follows:
default_pulse = solver.pulse(solver.embedding())default_pulse
Pulse(amp=InterpolatedWaveform(Points: (0, 1e-09), (3000, 15.71), (5999, 1e-09), Interpolator=PchipInterpolator) rad/µs, detuning=InterpolatedWaveform(Points: (0, -125.7), (3000, 0), (5999, 125.7), Interpolator=PchipInterpolator) rad/µs, phase=0, post_phase_shift=0)
Custom
Section titled “Custom”To design our own pulse shaping method, we need to define a class inhereting from mis.pipeline.pulse.BasePulseShaper
and implement a pulse
method and a detuning
method as follows:
from mis.pipeline.pulse import BasePulseShaperfrom pulser import Pulsefrom qoolqit._solvers import Detuning
class ConstantPulseShaper(BasePulseShaper): """ We simply return a constant pulse. """
def pulse( self, config: SolverConfig, register: Register, backend: BaseBackend, instance: MISInstance ) -> Pulse: import numpy as np return Pulse.ConstantPulse(1000, np.pi, 0, 0)
def detuning( self, config: SolverConfig, register: Register, backend: BaseBackend, instance: MISInstance ) -> list[Detuning]: return list()
constant_pulse_config = SolverConfig(backend = qutip_config, pulse_shaper=ConstantPulseShaper())constant_pulse_solver = MISSolver(instance, constant_pulse_config)constant_pulse = constant_pulse_solver.pulse(constant_pulse_solver.embedding())constant_pulse
Pulse(amp=ConstantWaveform(1000 ns, 3.14) rad/µs, detuning=ConstantWaveform(1000 ns, 0) rad/µs, phase=0, post_phase_shift=0)