Qruise Logo
All blog posts

Realistic Control Stack Modelling: Part II - Digital Analogue Converter

We are surrounded by digital devices, mobile phones, computers, audio players, and many other devices that use binary representations of information to process data and convert them to analogue signals. At the heart of this technology is the Digital Analogue Converter (DAC) that bridges the worlds of binary and continuous representation of signals. Think of the Riemann integral, where a continuous curve is broken into discrete rectangular pieces to approximate the area under the curve. The finer the discretised intervals are, the more accurate the area under the curve is. Similarly, a DAC estimates the amplitude of a continuous curve using discrete values. Every DAC is characterised by its number of bits (NOB) which indicates how precisely it can approximate continuous values. For instance, if a DAC has 4 NOBs and the range of its operational voltage is from -2.5V to 2.5V, the digital values of the voltage are 16 values in total i.e. {-2.5V, -2.1875V, -1.875V, ..., 1.5625V, 1.875V, 2.1875V} with the increment of 0.3125V. It is worth mentioning that, in practice, DAC devices have an effective number of bits (ENOB) due to noise and quantisation errors, a number always smaller than NOB.

Riemann Integral, discretisation of a contiuous curve. The finer the intervals are, the better the area under the curve is approximated. The same idea applies to DACs, the higher the NOB, the finer the representation of a continuous analogue signal would be.

In our previous blog, we discussed some of the features of AWGs modelled in the Qruise toolset. Yet, an AWG is inherently a digital device and thus has a DAC component. To replicate this behaviour, one can use the DigitalToAnalog device in the Qruise toolset and specify the NOB of the device. Since DigitalToAnalog is a pseudo-device as it is not autonomous by itself but rather comes in combination with other generic electronic devices such as an AWG, the content presented here can be considered as a natural extension to a more realistic AWG device. Let us assume that our AWG operates between -2.5V and 2.5V and has 16 NOB.

First, it is essential to declare the flux (drive) line first.

from qruise.toolset.libraries import components, hamiltonians

drive_line = components.Drive(
    name="flux_line",
    connected=["Qubit_0"],
    hamiltonian_func=hamiltonians.x_drive,
)

Then define the parameters of the AWG and its DAC component, construct the control stack and declare the connectivity of the components.

from qruise.toolset.objects import Quantity as Qt
from qruise.toolset.generator.devices import OEMAWG, DigitalToAnalog, VoltsToHertz

# simulation resolution, different than the AWG sampling rate
sim_res = 1e11

awg_params = {
    "name": "AWG",  # given name of the device
    "sim_time": 20e-9,  # 20ns (pulse duration)
    "samp_rate": int(2.4e9),  # 2.4 GHz sample rate
    "waveform_granularity": 8,  # granularity of the AWG device
    "min_waveform_len": 16,  # shortest (in number of samples) waveform AWG can produce
}


dac_params = {
    "nob": 16,  # number of bits
    "min_range": -2.5,  # minimum range of the output voltage
    "max_range": 2.5,  # maximum range of the output voltage
}


stack_devices = {
    "AWG": OEMAWG(**awg_params),
    "DAC": DigitalToAnalog(name="DAC", **dac_params),
    "V2Hz": VoltsToHertz(
        name="V2Hz", V_to_Hz=Qt(value=1.0, unit="V/Hz")
    ),
}

stack_chain = {"flux_line": {"AWG": [], "DAC": ["AWG"], "V2Hz": ["DAC"]}}

For a more appealing visualisation of the effect of the DAC component, let us define a Gaussian envelope so that the discretisation of the pulse is easily perceivable.

from qruise.toolset.signal import pulse

t_gate = stack_devices["AWG"].sim_time
gauss_params = {
    "amp": Qt(
        value=1.0, min_val=-2.5, max_val=2.5, unit="V"
    ),  # maximum amplitude of the Gaussian envelope
    "t_final": Qt(
        value=t_gate, unit="s"
    ),  # the final time at which the envelope is set to zero
    "sigma": Qt(value=0.25 * t_gate, unit="s"),  # the variance of the Gaussian envelope
}
gauss_env = pulse.Envelope(
    name="gauss_env",
    params=gauss_params,
    shape=pulse.envelopes.gaussian_nonorm,  # here we specify the shape of the envelope
)

And assign an identity instruction to the envelope.

from qruise.toolset.signal import gates
from qruise.toolset.libraries import constants

instr = gates.Instruction(
    name="id",  # name of the instruction
    t_end=t_gate,  # duration of the instruction (pulse duration)
    channels=["flux_line"],  # to which channel the instruction (pulse) is applied
    targets=[0],  # the index of the target qubit (not relevant but mandatory)
    ideal=constants.Id,
)

instr.add_component(comp=gauss_env, chan="flux_line")

We can now declare the Generator to produce the output pulse. Note that if we set the resolution of the Generator at low values, the step-like profile of the signal, due to the DAC component, may not be visible. We can set the resolution to a higher value to resolve those fine details.

from qruise.toolset.generator.generator import Generator

generator = Generator(devices=stack_devices, chains=stack_chain)
generator.draw(instr=instr, resolution=10 * stack_devices["AWG"].samp_rate)

Share this post

Stay Informed with Our Newsletter

Subscribe to our newsletter to get the latest updates on our products and services.

© Qruise GmbH 2024. All rights reserved
European Union flag

Funded by the European Union. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or the European Innovation Council and SMEs Execitve Agency (EISMEA). Neither the European Union nor the granting authority can be held responsible for them. Grant agreement No 101099538