Skip to content

Latest commit

 

History

History
275 lines (242 loc) · 9.51 KB

README.md

File metadata and controls

275 lines (242 loc) · 9.51 KB

Table of Contents

  1. Description
  2. Overview
  3. Features
  4. Example
  5. Usage
    1. Instruments
      1. Common Setups
      2. Disjoint Setups
    2. Connectivity
      1. Basic Elements (Superconducting Qubits)
      2. How to add digital triggers
      3. How to constrain where channels will be allocated
    3. Allocation
      1. Basic Allocation
      2. Re-using Channels
    4. Visualization

Description

The wirer module provides a way to auto-assigning channels for a collection of Quantum elements given a specific QM instrument setup.

Overview

The module decouples two parts of a quantum computing setup:

  • Instruments: Defines all accesssible Quantum Machines instruments.
  • Connectivity: Defines all functional quantum elements to be controlled by Quantum Machines instruments.

Once fully defined, the quantum elements can be mapped to channels on the Quantum Machines instruments using the allocate_wiring function:

graph TD
    A(Instruments) --> D["allocate_wiring()"]
    C(Connectivity) --> D["allocate_wiring()"]
    subgraph Wirer
        D["allocate_wiring()"]
    end
Loading

Features

The wirer tool supports the following features:

  • Assignment of channels to any combination of MW-FEM, LF-FEM, Octave or OPX+.
  • Any mapping of N resonator lines to M qubits.
  • Any mapping of N FEMs to OPX1000 chassis slots.
  • Constrained-scope allocation according to user preferences.
  • Natural overflowing during assignment to multiple slots, chassis, modules, octaves, etc.
  • Any combination of resonator, drive line, flux line, coupler line for each qubit.
  • Total visualization of the final connectivity.

Example

from qualang_tools.wirer import Instruments, Connectivity, allocate_wiring, visualize, lf_fem_spec

# Define instruments
instruments = Instruments()
instruments.add_lf_fem(controller=1, slots=[1, 2])
instruments.add_mw_fem(controller=1, slots=[3, 7])

qubits = [1, 2, 3, 4, 5, 6]
qubit_pairs = [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]

# Define quantum elements
connectivity = Connectivity()
connectivity.add_resonator_line(qubits=qubits, triggered=True)
connectivity.add_qubit_drive_lines(qubits=qubits, triggered=True)
connectivity.add_qubit_flux_lines(qubits=qubits)
connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubit_pairs, constraints=lf_fem_spec(out_slot=2))

# Allocate channels for each quantum element
allocate_wiring(connectivity, instruments)

# Visualize the result
visualize(connectivity.elements, instruments.available_channels)

opx1000 example

Usage

Instruments

Start with an empty instruments container:

from qualang_tools.wirer import Instruments

instruments = Instruments()

Using the "builder" pattern, you are able to define and add instruments to the container, line-by-line.

Common Setups

Below are examples of some instrument setups:

# Single OPX+
instruments.add_opx_plus(con=1)
# Single OPX+ and Octave
instruments.add_opx_plus(controllers=1)
instruments.add_octave(indices=1)
Image empty octave empty opx+
# Multiple OPXs and Octaves
instruments.add_opx_plus(controllers=[1, 2])
instruments.add_octave(indices=[1, 2])
# Single LF-FEM and Octave
instruments.add_lf_fem(controller=1, slots=[1])
instruments.add_octave(indices=1)
# Single LF-FEM and MW-FEM
instruments.add_lf_fem(controller=1, slots=[1])
instruments.add_mw_fem(controller=1, slots=[2])

Setups with External Mixers

Note: An external mixer is defined as abstractly as a combined, IQ-upconverter and IQ-downconverter instrument.

# Single LF-FEM and 2x External Mixers
instruments.add_lf_fem(controller=1, slots=[1])
instruments.add_external_mixer(indices=[1, 2])
# Single OPX+ and 2x External Mixers
instruments.add_opx_plus(controllers=[1, 2])
instruments.add_external_mixer(indices=[1, 2])
Image Empty OPX1000
# Multiple LF-FEMs and MW-FEMs
instruments.add_lf_fem(controller=1, slots=[1,2,3,4,5])
instruments.add_mw_fem(controller=1, slots=[6,7,8])

Disjoint Setups

The instruments container allows you to construct a "disjoint" setup, that is one where the indices are not logically ordered or belong only partially to a full cluster:

# Disjoint OPX+ and Octave addressing
instruments.add_opx_plus(controllers=[2, 5])
instruments.add_octave(indices=[1, 3, 8])
# Disjoint OPX+ and Octave addressing
instruments.add_lf_fem(controller=3, slots=[2, 4])
instruments.add_mw_fem(controller=3, slots=[1, 7])

Connectivity

The Connectivity class is like a blueprint for what quantum elements exist in our system, along with some optional information about how channels should be allocated for these elements.

To define your own Connectivity object, start with an empty container:

from qualang_tools.wirer import Connectivity

connectivity = Connectivity()

Basic Elements (Superconducting Qubits)

Now you can add "wiring specifications" to the container. These indicate what channels you need for a particular type of quantum functionality.

Note: Channels aren't being allocated at this stage- instead, we are adding "templates" for channels which will be allocated later.

# Define arbitrary set of qubits and qubit pairs for convenience
qubits = [1, 2, 5]
qubit_pairs = [(1, 2)]

# Define a single resonator feedline for multiple qubits
connectivity.add_resonator_line(qubits=qubits)

# Define a RF drive-lines for controlling multiple qubits
connectivity.add_qubit_drive_lines(qubits=qubits)

# Define DC lines for controlling flux
connectivity.add_qubit_flux_lines(qubits=qubits)

# Define DC lines for controlling tunable coupler
connectivity.add_qubit_pair_flux_lines(qubit_pairs=qubits)
Example on OPX+ and Octave Basic SC-qubit example octave Basic SC-qubit example opx

How to define digital triggers

connectivity.add_resonator_line(qubits=qubits, triggered=True)

How to constrain where channels will be allocated

In order to be more specific about the connectivity, we can add constraints. First, you need to select which type of constraint you will apply, based on the instruments you wish the line to be allocated. The types you can choose from include:

from qualang_tools.wirer import (
    mw_fem_spec,
    lf_fem_spec, 
    lf_fem_iq_spec,
    lf_fem_iq_octave_spec,
    opx_spec, 
    opx_iq_spec, 
    opx_iq_octave_spec,
    octave_spec
)

Then, you can set almost any combination of the available attributes according to what you want constrained. Here are some examples of how you could constrain something to a particular set of I/Q channels between an OPX+ and an Octave.

# Must allocate to OPX con1
constraint = opx_iq_octave_spec(con=1)
# Must allocate to OPX con2
constraint = opx_iq_octave_spec(con=2)
# Must allocate to Octave RF3
constraint = opx_iq_octave_spec(rf_out=3)
# Must allocate to OPX con1, I/Q channels 7 & 8, Octave RF3
constraint = opx_iq_octave_spec(con=1, out_port_i=7, out_port_q=8, rf_out=3)

After defining some constraint, you can apply it to a wiring specification:

connectivity.add_qubit_drive_lines(qubits=[1], constraints=constraint)

Allocation

Allocating channels

Once you have defined suitable Instruments and Connectivity objects, you can allocate channels using the allocate_wiring function:

from qualang_tools.wirer import allocate_wiring

allocate_wiring(connectivity, instruments)

At the time of allocation, all of the channel information is put inside the connectivity.elements dictionary.

Make certain channels re-usable

In order to double-up allocations to channels, you can keep them "free for allocation" even after doing a round of allocation. Here's an example of how you could allocate multiple qubits to the same channel, either if you don't have enough channels to measure every element, or if you'd like to multiplex the outputs:

connectivity = Connectivity()

connectivity.add_qubit_drive_lines(qubits=1)
allocate_wiring(connectivity, instruments, block_used_channels=False)

connectivity.add_qubit_drive_lines(qubits=2)
allocate_wiring(connectivity, instruments)

connectivity.add_qubit_drive_lines(qubits=3)
allocate_wiring(connectivity, instruments, block_used_channels=False)

connectivity.add_qubit_drive_lines(qubits=4)
allocate_wiring(connectivity, instruments)
Image empty octave empty opx+

Visualization

To visualize an allocated connectivity object, you can pass it into the visualization function:

visualize(connectivity.elements, available_channels=instruments.available_channels)

Note: Passing instruments.available_channels is optional, but it makes the plotting nicer by plotting white space on un-allocated channels!