OPX simulator

The simulator is capable of prediciting the actual output timing of the OPX for a given QUA program.

The simulator supports input to measurement commands via the loopback interface.

The simulator returns two type of objects:

  1. simulated analog wavefoms and simulated digital waveforms, which is a dictionary that contains the names, timing values, frequencies and phases of all waveforms that were played during a program. These can be retreived based on DAC ports or quantum elements.

    Note

    The samples of the waveforms are the raw envelope values, before modulation by the carrier, scaling with the amp() function, frame rotation and mixer correction. To obtain the actual values that are output to the DAC, see get_simulated_samples() method of QmJob.

  2. The actual analog samples that were played to the DAC, using the get_simulated_samples() method of QmJob.

Usage

By calling the simulate() function of a QuantumMachine (see Quantum Machine API). This function should receive a SimulationConfig object, whose API is specified in the following section.

An example for the a call to simulate can be found in its API documentation.

LoopbackInterface

It is possible to simulate a connection from an output of an OPX to one of the two inputs. This is done by using the LoopbackInterface which is passed to the simulate method of the QM object. For example, the following code generates a virtual connection from output 1 to input 2. A list of two such tuples can be passed to the LoopbackInterface (as there are two inputs to the OPX).

QM.simulate(prog,SimulationConfig(duration, LoopbackInterface([("con1",1,"con1",2)]))

LoopbackInterface can also simulate signal propagation delay and noise using the noisePower and latency keywords.

noisePower adds gaussian noise with zero mean and a variance (in units [V^2]) set by this parameter.

latency adds a signal propagation delay in [ns] set by this parameter

The following adds noise with variance 1 V^2 and 100 ns latency. All (up to 2) tuples in the loopback interface will have the same noise figure and same latency.

QM.simulate(prog,SimulationConfig(duration, LoopbackInterface([("con1",1,"con1",2)],noisePower=1,latency=100))

Raw ADC interface

It is possible to explicitly specify the signal passed into the OPX in the simulation. This is quite similar to the LoopbackInterface described above and supports the same options.

For example, the code snippet below playes a 1 microsecond signal to input 1 of the device called “con1”.

signal=np.linspace(0,0.2,1000).tolist()
QM.simulate(prog,SimulationConfig(duration, RawInterface([("con1",1,signal)],noisePower=1,latency=100))

API

class SimulationConfig(duration=0, include_analog_waveforms=False, include_digital_waveforms=False, simulation_interface=None, controller_connections=[])

create a configuration object to pass to QuantumMachine.simulate

Parameters
  • duration (int) – The duration to run the simulation for, in clock cycles

  • include_analog_waveforms (bool) – Should we collect simulated analog waveform names

  • include_digital_waveforms (bool) – Should we collect simulated digital waveform names

  • simulation_interface (SimulatorInterface) –

    interface to simulator. Currently supported interfaces:

    None - zero inputs

    LoopbackInterface - Loopback output to input (see API)

  • controller_connections (List[ControllerConnection]) – A list of connections between the controllers in the config

class LoopbackInterface(connections, latency=24, noisePower=0.0)

create a loopback interface for simulation

Parameters

connections (list) –

list of tuples with loopback connections. Each tuple can be:

  1. Physical connection between ports:

    (fromController: str, fromPort: int, toController: str, toPort: int)

  2. Virtual connection between quantum elements:

    (fromQE: str, toQE: str, toQEInput: int)

example:

>>> job = qm.simulate(prog2, SimulationConfig(
>>>                   duration=20000,
>>>                   # loopback from output 1 to input 1:
>>>                   simulation_interface=LoopbackInterface([("con1", 1, "con1", 1)])

Examples

from qm.QuantumMachinesManager import QuantumMachinesManager
from qm import SimulationConfig
from qm import LoopbackInterface
from qm.qua import *
from examples._config import config
import matplotlib.pyplot as plt

qmm = QuantumMachinesManager(host='127.0.0.1')
qm = qmm.open_qm(config)

##
# ## Example 1 - No inputs

with program() as prog1:
        a = declare(fixed)
        b = declare(fixed, value=0.2)
        c = declare(fixed, value=0.4)

        play("measurement", "qe1")
        play("marker", "qeDig")

        with for_(a, 0.2, a < 0.9, a + 0.1):
                play("measurement" * amp(a), "qe1")

        assign(c, c + b*1.5)
        save(c, "c")

# simulate program
job = qm.simulate(prog1, SimulationConfig(
        duration=1000,                  # duration of simulation in units of 4ns
        include_analog_waveforms=True,    # include analog waveform names
        include_digital_waveforms=True    # include digital waveform names
))

# get DAC and digital samples
samples = job.get_simulated_samples()

# plot all ports:
samples.con1.plot()

# another way, plot analog output 1 and digital output 9:
# plt.figure()
# plt.plot(samples.con1.analog["1"])
# plt.plot(samples.con1.digital["9"])
# plt.legend(("analog 1", "digital 9"))
# plt.xlabel("Time [ns]")
# plt.ylabel("Signal [V]")

# print waveform names and times
print(job.simulated_analog_waveforms())
print(job.simulated_digital_waveforms())

# get results
res = job.get_results()
c = res.variable_results.c.values
print(c)

##
# ## Example 2 - Loopback inputs

with program() as prog2:
        I = declare(fixed)
        Q = declare(fixed)
        f = declare(int)
        a = declare(fixed)

        with for_(a, 0.1, a < 0.4, a + 0.1):
                with for_(f, 50e6, f < 92e6, f + 1e6):
                        update_frequency("qe1", f)
                        measure("measurement"*amp(a), "qe1", "adc", demod.full('integW1', I), demod.full('integW2', Q))
                        save(I, "I")
                        save(Q, "Q")


# simulate program
job = qm.simulate(prog2, SimulationConfig(
        duration=40000,
        simulation_interface=LoopbackInterface([("con1", 1, "con1", 1)])    # loopback from output 1 to input 1
))


# get results
res = job.get_results()
adc = res.raw_results.input1.values
ts = res.raw_results.input1.ts_nsec
I = res.variable_results.I.values
Q = res.variable_results.Q.values

plt.figure()
plt.plot(ts, adc)
plt.xlabel("Time [ns]")
plt.ylabel("ADC")

plt.figure()
plt.plot(I, Q, '.')
plt.xlabel("I")
plt.xlabel("Q")

##
# ## Example 3:

with program() as prog3:
        d = declare(int)

        with for_(d, 10, d <= 100, d + 10):
                play("measurement", "qe1")
                play("measurement", "qe2", duration=d)
                wait(50, "qe1")


# simulate program
job = qm.simulate(prog3, SimulationConfig(
        duration=1700,                  # duration of simulation in units of 4ns
))


# get DAC and digital samples
samples = job.get_simulated_samples()

# plot analog ports 1 and 3:
samples.con1.plot(analog_ports={'1', '3'}, digital_ports={})

# another way:
# plt.figure()
# plt.plot(samples.con1.analog["1"], "-")
# plt.plot(samples.con1.analog["3"], "--")
# plt.legend(("analog 1", "analog 3"))
# plt.xlabel("Time [ns]")
# plt.ylabel("Signal [V]")