diff --git a/README.md b/README.md index 337eb880..e8d068f3 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,64 @@ -# QuAM (Quantum Abstract Machine) - -Welcome to QuAM! -The documentation is currently being added to the QM documentation. In the mean time, -you can view the documentation files in `docs`. -In particular, this includes installation instructions at `docs/getting-started.md`. - -You can also run the documentation website locally as follows: -1. Open terminal / Powershell and navigate to the root QuAM folder -2. Ensure you are in a virtual environment. - See installation instructions in `docs/getting-started.md` for details -2. Run the following commands: -``` -pip install ".[docs]" -mkdocs serve +Here's a README template for the QuAM GitHub repository front page. This README provides a concise overview of QuAM, useful links, installation instructions, and a placeholder for code examples: + +--- + +# QuAM: Quantum Abstract Machine + +## Overview +QuAM (Quantum Abstract Machine) is an innovative software framework designed to provide an abstraction layer over the QUA programming language, facilitating a more intuitive interaction with quantum computing platforms. Aimed primarily at physicists and researchers, QuAM allows users to think and operate in terms of qubits and quantum operations rather than the underlying hardware specifics. + +Explore detailed documentation and get started with QuAM here: [QuAM Documentation](ENTER_URL_HERE). + + +## Key Features +- **Abstraction Layer**: Simplifies quantum programming by providing higher-level abstractions for qubit operations. +- **Component-Based Structure**: Utilize modular components like Mixers and IQChannels for flexible quantum circuit design. +- **Automated Configuration**: Generate QUA configurations from QuAM setups seamlessly. +- **Extensibility**: Extend QuAM with custom classes to handle complex quantum computing scenarios. +- **State Management**: Features robust tools for saving and loading your quantum states, promoting reproducibility and consistency. + +## Installation +To install QuAM, follow these simple steps: + +1. Ensure you have Python ≥ 3.8 installed on your system. +2. Clone the repository: + ```bash + git clone https://github.com/qua-platform/quam.git + ``` +3. Navigate to the cloned directory and install the required dependencies: + ```bash + cd quam + pip install . + ``` + +## Quick Start +Here’s a basic example to get you started with QuAM: + +```python +from quam.components import BasicQuAM, SingleChannel, pulses + +# Create a root-level QuAM instance +machine = BasicQuAM() + +# Add an OPX output channel +channel = SingleChannel(opx_output=("con1", 1)) +machine.channels["output"] = channel + +# Add a Gaussian pulse to the channel +channel.operations["gaussian"] = pulses.Gaussian( + length=100, # Pulse length in ns + amplitude=0.5, # Peak amplitude of Gaussian pulse + sigma=20, # Standard deviation of Guassian pulse +) + +# Play the Gaussian pulse on the channel within a QUA program +with program() as prog: + channel.play("gaussian") + +# Generate the QUA configuration from QuAM +qua_configuration = machine.generate_config() ``` -3. Navigate to `127.0.0.1:8000` \ No newline at end of file + + +## License +QuAM is released under the BSD-3 License. See the LICENSE file for more details. diff --git a/docs/API_references/components/basic_quam_API.md b/docs/API_references/components/basic_quam_API.md new file mode 100644 index 00000000..4139905d --- /dev/null +++ b/docs/API_references/components/basic_quam_API.md @@ -0,0 +1,3 @@ +# BasicQuAM API + +::: quam.components.basic_quam \ No newline at end of file diff --git a/docs/API_references/components/channels_API.md b/docs/API_references/components/channels_API.md new file mode 100644 index 00000000..cacb6991 --- /dev/null +++ b/docs/API_references/components/channels_API.md @@ -0,0 +1,3 @@ +# QuAM Channels API + +::: quam.components.channels \ No newline at end of file diff --git a/docs/API_references/components/hardware_API.md b/docs/API_references/components/hardware_API.md new file mode 100644 index 00000000..2fa536ef --- /dev/null +++ b/docs/API_references/components/hardware_API.md @@ -0,0 +1,3 @@ +# QuAM Hardware API + +::: quam.components.hardware \ No newline at end of file diff --git a/docs/API_references/components/octave_API.md b/docs/API_references/components/octave_API.md new file mode 100644 index 00000000..14b3f80e --- /dev/null +++ b/docs/API_references/components/octave_API.md @@ -0,0 +1,11 @@ +# Welcome to the QuAM Octave API Documentation + +The Octave component in the Quantum Abstract Machine (QuAM) manages signal upconversion and downconversion through its frequency converters. This section provides an API guide for setting up and customizing the Octave, detailing its integration with quantum processors for efficient signal processing. Explore the capabilities, configuration options, and practical examples to enhance your quantum operations with Octave's advanced functionalities. + +::: quam.components.octave + options: + members: + - Octave + - OctaveFrequencyConverter + - OctaveUpConverter + - OctaveDownConverter diff --git a/docs/API_references/components/pulses_API.md b/docs/API_references/components/pulses_API.md new file mode 100644 index 00000000..fb80d8cb --- /dev/null +++ b/docs/API_references/components/pulses_API.md @@ -0,0 +1,9 @@ +# QuAM Pulses API + +Welcome to the QuAM Pulses API Documentation. +The QuAM Pulses module offers a versatile framework for creating and controlling pulse schemes essential for quantum operations. +Information can be found in [QuAM Pulses Documentation](/components/pulses) in the User Guide. + +This section provides detailed API references for various pulse types—ranging from simple waveforms to complex modulated pulses—tailored for precise quantum state manipulation and measurement. Explore the properties, methods, and examples to effectively integrate these pulse components into your quantum experiments. + +::: quam.components.pulses \ No newline at end of file diff --git a/docs/API_references/core/quam_classes_API.md b/docs/API_references/core/quam_classes_API.md new file mode 100644 index 00000000..2fda0cc7 --- /dev/null +++ b/docs/API_references/core/quam_classes_API.md @@ -0,0 +1,11 @@ +# QuAM Classes API + +::: quam.core.quam_classes + handler: python + options: + members: + - QuamBase + - QuamRoot + - QuamComponent + - QuamDict + - QuamList \ No newline at end of file diff --git a/docs/API_references/core/quam_instantiation.md b/docs/API_references/core/quam_instantiation.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/API_references/index.md b/docs/API_references/index.md new file mode 100644 index 00000000..90a973fe --- /dev/null +++ b/docs/API_references/index.md @@ -0,0 +1,24 @@ +# QuAM API Reference + +Welcome to the API Reference section of the Quantum Abstract Machine (QuAM) documentation. Here, you'll find comprehensive details on all components, classes, and methods that make up the QuAM framework. This documentation is designed to help developers understand and effectively utilize the powerful capabilities of QuAM for quantum computing applications. + +## Quick Links + +- [**Core Components**](/API_references/core/quam_classes_API) + Details on fundamental building blocks like [QuamBase][quam.core.quam_classes.QuamBase], [QuamComponent][quam.core.quam_classes.QuamComponent] and [QuamRoot][quam.core.quam_classes.QuamRoot]. + [QuamBase][quam.core.quam_classes.QuamBase] + +- [**Channel Components**](/API_references/components/channels_API) + Learn about channel configurations and their operations within the QuAM framework. + +- [**Pulse Components**](/API_references/components/pulses_API) + A detailed look at various pulse types and their properties used in quantum operations. + +- [**Hardware Components**](/API_references/components/hardware_API) + Explore the hardware-related classes such as [Mixer][quam.components.hardware.Mixer], [LocalOscillator][quam.components.hardware.LocalOscillator], and [FrequencyConverter][quam.components.hardware.FrequencyConverter]. + +- [**Octave Components**](/API_references/components/octave_API) + Documentation on the `Octave` component and its associated up and down converters. + +- [**BasicQuAM Class**](/API_references/components/basic_quam_API) + Details on the `BasicQuAM` class, the root-level QuAM instance that serves as the entry point for QuAM configurations. \ No newline at end of file diff --git a/docs/assets/qm_logo_white.svg b/docs/assets/qm_logo_white.svg new file mode 100644 index 00000000..f1b10556 --- /dev/null +++ b/docs/assets/qm_logo_white.svg @@ -0,0 +1,197 @@ + + + + + + image/svg+xml + + 2019-03-QUA-4284-I-Logo_R1 + + + + + + + + + + + + + + + + + + + + + + + + + + + 2019-03-QUA-4284-I-Logo_R1 + + + + + + + + + diff --git a/docs/components/channels.md b/docs/components/channels.md index 61bb7a16..26e2aae8 100644 --- a/docs/components/channels.md +++ b/docs/components/channels.md @@ -1,6 +1,161 @@ # Channels -## Digital channels +In the QuAM library, channels are a fundamental concept that represent the physical connections to the quantum hardware. They are defined in the [quam.components.channels][quam.components.channels] module. + +We distinguish between the following channel types, where the terms "output" and "input" are always from the perspective of the OPX hardware: + +**1. Analog output channels** + +- [SingleChannel][quam.components.channels.SingleChannel]: Represents a single OPX output channel. +- [IQChannel][quam.components.IQChannel]: Represents an IQ OPX output channel. + +**2. Analog output + input channels** + +- [InOutSingleChannel][quam.components.channels.InOutSingleChannel]: Represents a single OPX output + input channel. +- [InOutIQChannel][quam.components.channels.InOutIQChannel]: Represents an IQ OPX output + input channel. + +**3. Digital channels** + +- [DigitalOutputChannel][quam.components.channels.DigitalOutputChannel]: Represents a digital output channel. + +Each analog [Channel][quam.components.channels.Channel] corresponds to an element in QUA, whereas the digital channel is part of an analog channel. + +These channel combinations cover most use cases, although there are exceptions (input-only channels and single-output, IQ-input channels) which will be implemented in a subsequent QuAM release. If you need such channels, please create a [Github issue](https://github.com/qua-platform/quam/issues). + + +## Analog Output Channels + +Analog output channels are the primary means of controlling the quantum hardware. They can be used to send various types of signals, such as microwave or RF signals, to control the quantum system. The two types of analog output channels are the [SingleChannel][quam.components.channels.SingleChannel] and the [IQChannel][quam.components.channels.IQChannel]. + + +### Analog Channel Ports + +A [SingleChannel][quam.components.channels.SingleChannel] is always attached to a single OPX output port, and similarly an [IQChannel][quam.components.channels.IQChannel] has an associated pair of IQ ports: + +```python +from quam.components import SingleChannel, IQChannel + +single_channel = SingleChannel( + opx_output=("con1", 1), + ... +) +IQ_channel = IQChannel( + opx_output_I=("con1", 2), + opx_output_Q=("con1", 3), + ... +) +``` + + +### DC Offset +Each analog channel can have a specified DC offset that remains for the duration of the QUA program. +This can be set through `SingleChannel.opx_output_offset` for the [SingleChannel][quam.components.channels.SingleChannel], and through `IQChannel.opx_output_offset_I` and `IQChannel.opx_output_offset_Q` for the [IQChannel][quam.components.channels.IQChannel]. + +Note that if multiple channels are attached to the same OPX output port(s), they may not have different output offsets. +This raises a warning and chooses the DC offset of the last channel. + +The DC offset can also be modified while a QUA program is running: +```python +from qm.qua import program + +with program() as prog: + single_channel.set_dc_offset(offset=0.1) + IQ_channel.set_dc_offset(offset=0.25, element_input="I") # Set offset of port I +``` +The offsets can also be QUA variables. +[Channel.set_dc_offset()][quam.components.channels.SingleChannel.set_dc_offset] is a light wrapper around `qm.qua.set_dc_offset` to attach it to the channel. + + +### Frequency Converters +The `IQChannel` is usually connected to a mixer to upconvert the signal using a local oscillator. +This frequency upconversion is represented in QuAM by a [FrequencyConverter][quam.components.hardware.FrequencyConverter] + +```python +from quam.components.hardware import FrequencyConverter, LocalOscillator, Mixer + +IQ_channel = IQChannel( + opx_output_I=("con1", 2), + opx_output_Q=("con1", 3), + intermediate_frequency=100e6, # Hz + frequency_converter=FrequencyConverter( + local_oscillator=LocalOscillator(frequency=6e9, power=10), + mixer=Mixer(), + ) +) +``` + +Integrated frequency conversion systems such as [QM's Octave](https://docs.quantum-machines.co/1.1.7/qm-qua-sdk/docs/Hardware/octave/) usually have additional features such as auto-calibration. +For this reason they have a specialized frequency converter such as the [OctaveUpConverter][quam.components.octave.OctaveUpConverter]. +See the [QuAM Octave Documentation][octave] documentation for details. + + +### Analog Pulses +QuAM has a range of standard [Pulse][quam.components.pulses.Pulse] components in [quam.components.pulses][quam.components.pulses]. +These pulses can be registered as part of the analog channel via `Channel.operations` such that the channel can output the associated pulse waveforms: + +```python +from quam.components import pulses + +channel.operations["X180"] = pulses.SquarePulse( + amplitude=0.1, # V + length=16, # ns +) +``` + +Once a pulse has been registered in a channel, it can be played within a QUA program: + +```python +with program() as prog: + channel.play("X180") +``` +[Channel.play()][quam.components.channels.Channel.play] is a light wrapper around [qm.qua.play()](https://docs.quantum-machines.co/latest/qm-qua-sdk/docs/Introduction/qua_overview/?h=play#play-statement) to attach it to the channel. + +Details on pulses in QuAM can be found at the [Pulses Documentation][pulses]. + +## Analog Output + Input Channels +Aside from sending signals to the quantum hardware, data is usually also received back, and subsequently read out through the hardware's input ports. +In QuAM, this is represented using the [InOutSingleChannel][quam.components.channels.InOutSingleChannel] and the [InOutIQChannel][quam.components.channels.InOutIQChannel]. +These channels don't only have associated output port(s) but also input port(s): + +```python +from quam.components import InOutSingleChannel, InOutIQChannel + +single_io_channel = InOutSingleChannel( + opx_output=("con1", 1), + opx_input=("con1", 1) + ... +) +IQ_io_channel = InOutIQChannel( + opx_output_I=("con1", 2), + opx_output_Q=("con1", 3), + opx_input_I=("con1", 1), + opx_input_Q=("con1", 2) + ... +) +``` + +These are extensions of the [SingleChannel][quam.components.channels.SingleChannel] and the [IQChannel][quam.components.channels.IQChannel] that add relevant features for readout. + +Both the [InOutSingleChannel][quam.components.channels.InOutSingleChannel] and the [InOutIQChannel][quam.components.channels.InOutIQChannel] combine output + input as in most cases a signal is also sent to probe the quantum hardware. +Support for input-only analog channels is planned for a future release. + + +### Readout Pulses +Channels that have input ports can also have readout pulses: + +```python +from quam.components import pulses +io_channel.operations["readout"] = pulses.SquareReadoutPulse( + length=16, # ns + amplitude=0.1, # V + integration_weights_angle=0.0, # rad, optional rotation of readout signal +) +``` +As can be seen, the readout pulse (in this case [SquareReadoutPulse][quam.components.pulses.SquareReadoutPulse]) is similar to the regular pulses, but with additional parameters for readout. +Specifically, it contains the attributes `integration_weights_angle` and `integration_weights` to specify how the readout signal should be integrated. + + +## Digital Channels QuAM supports digital output channels (output from the OPX perspective) through the component [DigitalOutputChannel][quam.components.channels.DigitalOutputChannel]. These can be added to any analog channel through the attribute `Channel.digital_outputs`. As an example: @@ -25,7 +180,8 @@ analog_channel.digital_outputs = { ``` In this case, any digital pulses will be played to all digital channels. -### Digital-only channel + +### Digital-only Channel It is also possible to create a digital-only channel, i.e. using digital ports without any analog ports. ```python from quam.components import Channel, DigitalOutputChannel @@ -35,7 +191,8 @@ channel = Channel( ) ``` -## Digital pulses + +## Digital Pulses Once a [DigitalOutputChannel][quam.components.channels.DigitalOutputChannel] is added to a [Channel][quam.components.channels.Channel], digital waveforms can be played on it. This is done by attaching a digital waveform to a [Pulse][quam.components.pulses.Pulse] through the attribute `Pulse.digital_marker`: ```python @@ -49,7 +206,8 @@ pulse = pulses.SquarePulse( ``` In the example above, the square pulse will also output digital waveform: "high" for 20 ns ⇨ "low" for 20 ns ⇨ "high" for 40 ns. This digital waveform will be played on all digital channels that are attached to the analog channel. -### Digital-only pulses + +### Digital-only Pulses A digital pulse can also be played without a corresponding analog pulse. This can be done by directly using the base [pulses.Pulse][quam.components.pulses.Pulse] class: ```python diff --git a/docs/custom-components.md b/docs/components/custom-components.md similarity index 91% rename from docs/custom-components.md rename to docs/components/custom-components.md index 63acd510..d75f4b7e 100644 --- a/docs/custom-components.md +++ b/docs/components/custom-components.md @@ -1,11 +1,11 @@ -# Custom components +# Custom QuAM Components To create custom QuAM components, their classes should be defined in a Python module that can be accessed from Python. The reason for this is that otherwise QuAM cannot load QuAM from a JSON file as it cannot determine where the classes are defined. If you already have a Python module that you use for your own QUA code, it is recommended to add QuAM components to that module. If you don't already have such a module, please follow the guide below. -## Creating a custom Python module +## Creating a Custom Python Module Here we describe how to create a minimal Python module that can be used for your custom QuAM components. In this example, we will give the top-level folder the name `my-quam` and the Python module will be called `my_quam` (note the underscore instead of dash). First create the following folder structure @@ -49,7 +49,7 @@ from my_quam.components import * ``` All the custom QuAM components should be placed as Python files in `my-quam/my_quam/components`. -## Creating a custom QuAM component +## Creating a Custom QuAM Component Once a designated Python module has been chosen / created, it can be populated with a custom component. We will assume that the newly-created Python module `my_quam` is used. In this example, we will make a basic QuAM component representing a DC gate, with two properties: `name` and `dc_voltage`: @@ -71,7 +71,7 @@ dc_gate = DcGate(id="plunger_gate", dc_voltage=0.43) A few notes about the above: -- Each QuamComponent inherits from [quam.core.quam_classes.QuamComponent][]. +- Each QuamComponent inherits from [QuamComponent][quam.core.quam_classes.QuamComponent]. - QuAM components are decorated with `@quam_dataclass`, which is a variant of the Python [@dataclass](https://docs.python.org/3/library/dataclasses.html). /// details | Reason for `@quam_dataclass` instead of `@dataclass` @@ -113,10 +113,10 @@ An additional benefit is that `kw_only=True` is automatically passed along. From Python 3.10 onwards, `@quam_dataclass` is equivalent to `@dataclass(kw_only=True, eq=False)` /// -## QuAM component subclassing +## QuAM Component Subclassing QuAM components can also be subclassed to add functionalities to the parent class. For example, we now want to combine a DC and AC gate together, where the AC part corresponds to an OPX channel. -To do this, we create a class called `AcDcGate` that inherits from both `DcGate` and [quam.components.channels.SingleChannel][]: +To do this, we create a class called `AcDcGate` that inherits from both `DcGate` and [SingleChannel][quam.components.channels.SingleChannel]: ```python from quam.components import SingleChannel @@ -124,6 +124,7 @@ from quam.components import SingleChannel @quam_dataclass class AcDcGate(DcGate, SingleChannel): + pass ``` It can be instantiated using @@ -131,4 +132,4 @@ It can be instantiated using ac_dc_gate = AcDcGate(id="plunger_gate", dc_voltage=0.43, opx_output=("con1", 1)) ``` -Notice that the keyword argument `opx_output` now also needs to be passed. This is because it's a required argument for [quam.components.channels.SingleChannel][]. +Notice that the keyword argument `opx_output` now also needs to be passed. This is because it's a required argument for [SingleChannel][quam.components.channels.SingleChannel]. diff --git a/docs/components/index.md b/docs/components/index.md new file mode 100644 index 00000000..ee56fd1d --- /dev/null +++ b/docs/components/index.md @@ -0,0 +1,23 @@ +# QuAM Components + +The components section of the Quantum Abstract Machine (QuAM) documentation outlines the modular parts of the QuAM framework, each designed to enable flexible and efficient quantum programming. Below, you'll find an overview of the main components that you can utilize and extend in your quantum projects. + +## Channels +Channels are fundamental building blocks in QuAM that facilitate the routing of quantum signals to various hardware components. They serve as the conduits for pulses and other quantum operations, translating abstract quantum actions into physical outcomes on a quantum processor. + +- **[Channels Documentation](channels.md)**: Explore the detailed documentation on different types of channels including IQ Channels, Single Analog Output Channels, and more. + +## Pulses +Pulses in QuAM are used to manipulate qubit states through precise control over their quantum properties. These are defined with specific parameters like amplitude, duration, and waveform, allowing detailed control over quantum operations. + +- **[Pulses Documentation](pulses.md)**: Learn how to define and use different types of pulses such as Gaussian, Square, and DRAG pulses in your quantum circuits. + +## Octave +The Octave component in QuAM handles the upconversion and downconversion of frequencies, enabling high-fidelity signal processing for quantum experiments. This is particularly important for setups requiring complex signal manipulations across multiple frequency bands. + +- **[Octave Documentation](octave.md)**: Discover how to integrate and configure Octave components to work seamlessly within your QuAM environment. + +## Custom QuAM Components +For users looking to expand beyond the standard QuAM toolkit, custom components provide a way to introduce novel functionalities tailored to specific quantum computing needs or experimental setups. + +- **[Custom QuAM Components](/components/custom-components)**: Get guidance on how to develop and integrate your own custom components into the QuAM framework. diff --git a/docs/components/octave.md b/docs/components/octave.md index c93e60a9..b0bc26a3 100644 --- a/docs/components/octave.md +++ b/docs/components/octave.md @@ -7,7 +7,7 @@ Below we describe the three steps needed to configuring an Octave in QuAM: 2. Adding frequency converters 3. Attaching channels -## :zero: Creating the root QuAM machine +## :zero: Creating the Root QuAM Machine Before we get started, we need a top-level QuAM class that matches our components: ```python @@ -30,11 +30,7 @@ This will be used later to generate our QUA configuration Below we show how an Octave is instantiated using some example arguments: ```python -octave = Octave( - name="octave1", - ip="127.0.0.1", - port=80, -) +octave = Octave(name="octave1", ip="127.0.0.1", port=80) machine.octave = octave ``` @@ -49,13 +45,13 @@ qmm = QuantumMachinesManager(host={opx_host}, port={opx_port}, octave=octave_con At this point the channel connectivity of the Octave hasn't yet been configured. We can do so by adding frequency converters. -## :two: Adding frequency converters +## :two: Adding Frequency Converters A frequency converter is a grouping of the components needed to upconvert or downconvert a signal. These typically consist of a local oscillator, mixer, as well as IF, LO, and RF ports. For the Octave we have two types of frequency converters: - [OctaveUpConverter][quam.components.octave.OctaveUpConverter]: Used to upconvert a pair of IF signals to an RF signal -- [OctaveDownCovnerter][quam.components.octave.OctaveDownConverter]: Used to downconvert an RF signal to a pair of IF signals +- [OctaveDownConverter][quam.components.octave.OctaveDownConverter]: Used to downconvert an RF signal to a pair of IF signals We can add all relevant frequency converters as follows: @@ -142,7 +138,7 @@ It is important to specify the `LO_frequency` of the frequency converters that a At this point, our `Octave` does not yet contain any information on which OPX output / input is connected to each `OctaveUpconverter` / `OctaveDownConverter`. This is done in the third stage -## :three: Attaching channels +## :three: Attaching Channels Once the frequency converters have been setup, it is time to attach the ones that are in use to corresponding channels in QuAM. In the example below, we connect an `IQChannel` to the `OctaveUpconverter` at `octave.RF_outputs[1]` ```python @@ -173,7 +169,7 @@ octave.RF_outputs[2].LO_frequency = 2e9 octave.RF_inputs[2].LO_frequency = 2e9 ``` -## Generating the config +## Generating the Config Once everything is setup, we can generate the QUA configuration ```python @@ -183,142 +179,93 @@ qua_config = machine.generate_config() /// details | qua_config ```json { - "version": 1, - "controllers": { - "con1": { - "analog_outputs": { - "1": { - "offset": 0.0 - }, - "2": { - "offset": 0.0 - }, - "3": { - "offset": 0.0 - }, - "4": { - "offset": 0.0 + "version": 1, + "controllers": { + "con1": { + "analog_outputs": { + "1": {"offset": 0.0}, + "2": {"offset": 0.0}, + "3": {"offset": 0.0}, + "4": {"offset": 0.0}, + }, + "digital_outputs": {}, + "analog_inputs": {"1": {"offset": 0.0}, "2": {"offset": 0.0}}, } - }, - "digital_outputs": {}, - "analog_inputs": { - "1": { - "offset": 0.0 + }, + "elements": { + "IQ1": { + "operations": {}, + "intermediate_frequency": 0.0, + "RF_inputs": {"port": ["octave1", 1]}, }, - "2": { - "offset": 0.0 + "IQ2": { + "operations": {}, + "intermediate_frequency": 0.0, + "RF_inputs": {"port": ["octave1", 2]}, + "smearing": 0, + "time_of_flight": 24, + "RF_outputs": {"port": ["octave1", 1]}, + }, + }, + "pulses": { + "const_pulse": { + "operation": "control", + "length": 1000, + "waveforms": {"I": "const_wf", "Q": "zero_wf"}, } - } - } - }, - "elements": { - "IQ1": { - "operations": {}, - "intermediate_frequency": 0.0, - "RF_outputs": { - "port": [ - "octave1", - 1 - ] - } }, - "IQ2": { - "operations": {}, - "intermediate_frequency": 0.0, - "RF_outputs": { - "port": [ - "octave1", - 2 - ] - }, - "smearing": 0, - "time_of_flight": 24, - "RF_inputs": { - "port": [ - "octave1", - 1 - ] - } - } - }, - "pulses": { - "const_pulse": { - "operation": "control", - "length": 1000, - "waveforms": { - "I": "const_wf", - "Q": "zero_wf" - } - } - }, - "waveforms": { - "zero_wf": { - "type": "constant", - "sample": 0.0 + "waveforms": { + "zero_wf": {"type": "constant", "sample": 0.0}, + "const_wf": {"type": "constant", "sample": 0.1}, }, - "const_wf": { - "type": "constant", - "sample": 0.1 - } - }, - "digital_waveforms": { - "ON": { - "samples": [ - [ - 1, - 0 - ] - ] - } - }, - "integration_weights": {}, - "mixers": {}, - "oscillators": {}, - "octaves": { - "octave1": { - "RF_outputs": { - "1": { - "LO_frequency": 2000000000.0, - "LO_source": "internal", - "gain": 0, - "output_mode": "always_off", - "input_attenuators": "off", - "I_connection": [ - "con1", - 1 - ], - "Q_connection": [ - "con1", - 2 - ] - }, - "2": { - "LO_frequency": 2000000000.0, - "LO_source": "internal", - "gain": 0, - "output_mode": "always_off", - "input_attenuators": "off", - "I_connection": [ - "con1", - 3 - ], - "Q_connection": [ - "con1", - 4 - ] + "digital_waveforms": {"ON": {"samples": [[1, 0]]}}, + "integration_weights": {}, + "mixers": {}, + "oscillators": {}, + "octaves": { + "octave1": { + "RF_outputs": { + "1": { + "LO_frequency": 2000000000.0, + "LO_source": "internal", + "gain": 0, + "output_mode": "always_off", + "input_attenuators": "off", + "I_connection": ["con1", 1], + "Q_connection": ["con1", 2], + }, + "2": { + "LO_frequency": 2000000000.0, + "LO_source": "internal", + "gain": 0, + "output_mode": "always_off", + "input_attenuators": "off", + "I_connection": ["con1", 3], + "Q_connection": ["con1", 4], + }, + }, + "IF_outputs": { + "IF_out1": {"port": ["con1", 1], "name": "out1"}, + "IF_out2": {"port": ["con1", 2], "name": "out2"}, + }, + "RF_inputs": { + "1": { + "RF_source": "RF_in", + "LO_frequency": 2000000000.0, + "LO_source": "internal", + "IF_mode_I": "direct", + "IF_mode_Q": "direct", + } + }, + "loopbacks": [], } - }, - "IF_outputs": {}, - "RF_inputs": {}, - "loopbacks": [] - } - } + }, } ``` /// -## Combined example +## Combined Example ```python from typing import Dict from dataclasses import field @@ -367,7 +314,7 @@ machine.channels["IQ2"] = InOutIQChannel( octave.RF_outputs[2].channel = machine.channels["IQ2"].get_reference() octave.RF_inputs[1].channel = machine.channels["IQ2"].get_reference() octave.RF_outputs[2].LO_frequency = 2e9 -octave.RF_inputs[2].LO_frequency = 2e9 +octave.RF_inputs[1].LO_frequency = 2e9 qua_config = machine.generate_config() diff --git a/docs/components/pulses.md b/docs/components/pulses.md new file mode 100644 index 00000000..41d0a03f --- /dev/null +++ b/docs/components/pulses.md @@ -0,0 +1,136 @@ +# Pulses +In the QuAM framework, pulses are the fundamental building blocks for crafting signals that interact with quantum processors. +These parametrized representations of waveforms, emitted from the OPX analog outputs, allow precise control over the shape and timing of signals. +For instance, a square pulse, defined by its amplitude and duration, can be used to initialize a quantum state or implement a gate operation. +This section explains how pulses are used in QuAM to facilitate quantum computing experiments. + +All pulses in QuAM are instances of the [Pulse][quam.components.pulses.Pulse] class. The QuAM library includes several predefined pulse types, such as: + +- **[SquarePulse][quam.components.pulses.SquarePulse]**: Typically used for simple quantum operations like flips or resets, characterized by a constant amplitude throughout its duration. +- **[GaussianPulse][quam.components.pulses.GaussianPulse]**: Ideal for minimizing spectral leakage due to its smooth rise and fall, commonly used in operations requiring high fidelity. +- **[DragPulse][quam.components.pulses.DragPulse]**: Designed to correct phase errors in quantum gates, enhancing the accuracy of operations involving superconducting qubits. + +The full list of predefined pulses can be found in the [pulses][quam.components.pulses] module. +Users can also define custom pulses by subclassing the `Pulse` class. This flexibility allows the creation of tailored waveforms that suit specific experimental requirements. + +All pulses in QuAM are instances of the [Pulse][quam.components.pulses.Pulse] class. +The QuAM library contains a set of common pulse types in the [pulses][quam.components.pulses] module. +Typical examples are [SquarePulse][quam.components.pulses.SquarePulse], [GaussianPulse][quam.components.pulses.GaussianPulse], and [DragPulse][quam.components.pulses.DragPulse]. +Users can supplement these common pulses with their own custom pulses by subclassing the [Pulse][quam.components.pulses.Pulse] class (see [Custom QuAM Components](/components/custom-components) for details). + + +## Usage +To implement pulses in a QuAM program, you first need to register them to a specific channel. Here's how to set up a channel and register a square pulse for an operation labeled `"X180"`: + +```python +from quam.components import pulses, SingleChannel + +# Create a channel associated with the first output on connector 1 +channel = SingleChannel(opx_output=("con1", 1)) + +# Register a square pulse with a duration of 1000 units and amplitude of 0.5 +channel.operations["X180"] = pulses.SquarePulse(duration=1000, amplitude=0.5) +``` + +After registering a pulse, you can utilize it in a QuAM program. Below is a simple example where the `"X180"` pulse is played: + +```python +from qm.qua import program + +# Start a new QuAM program +with program() as prog: + # Play the "X180" pulse on the previously defined channel + channel.play("X180") +``` + +## Readout Pulses +In addition to control pulses, QuAM also supports readout pulses, which are used to measure the state of a quantum system. +These pulses should be attached to an input channel, either [InOutIQChannel][quam.components.channels.InOutIQChannel] or [InOutSingleChannel][quam.components.channels.InOutSingleChannel]. + +Here's an example of how to define a readout pulse for a channel: + +```python +readout_channel.operations["readout"] = pulses.SquareReadoutPulse( + length=1000, + amplitude=0.1 + integration_weights=[(1, 500)] +) +``` + +Once a readout pulse is defined, it can be used in a QuAM program to measure the state of the quantum system: + +```python +with program() as prog: + # Measure the state of the quantum system using the "readout" pulse + qua_result = readout_channel.measure("readout") +``` + +## Creating Custom Pulses +To create custom pulses in QuAM, you can extend the functionality of the Pulse class by subclassing it and defining your own waveform generation logic. This allows for precise control over the pulse characteristics. + +### Example: Creating a Ramp Pulse +To illustrate, let's create a pulse that ramps in amplitude. This involves subclassing the Pulse class from the QuAM library and defining specific parameters and the waveform function. + +```python +import numpy as np +from quam.core import quam_dataclass +from quam.components import pulses + +@quam_dataclass +class RampPulse(pulses.Pulse): + # Define the starting and stopping amplitudes for the ramp pulse + amplitude_start: float + amplitude_stop: float + + def waveform_function(self) -> np.ndarray: + # This function generates a linearly spaced array to form a ramp waveform + return np.linspace(self.amplitude_start, self.amplitude_stop, self.length) +``` +Ensure this code is saved in a properly structured Python module within your project so that it can be imported as needed. For details on organizing custom components, refer to the [Custom QuAM Components](/components/custom-components) section of the QuAM documentation + +### Extending to Readout Pulses +To create a readout pulse derived from a control pulse, subclass both the specific control pulse and the [ReadoutPulse][quam.components.pulses.ReadoutPulse] class. Below is an example of how to adapt the RampPulse into a readout pulse. + +```python +@quam_dataclass +class RampReadoutPulse(pulses.ReadoutPulse, RampPulse): + """Extend RampPulse to include readout-specific functionality.""" + # No additional fields needed; inherits all from RampPulse and ReadoutPulse + pass +``` +Readout pulses utilize additional parameters for integration weights which are crucial for signal processing: + +- **`ReadoutPulse.integration_weights`**: A list of floats or tuples specifying the weights over time. +- **`ReadoutPulse.integration_weights_angle`**: The angle (in radians) applied to the integration weights. + +These two parameters are used to calculate the readout pulse's integration weights (`sine`, `-sine` and `cosine`), which are essential for signal processing in readout operations. + +These parameters are typically used to manage the integration weights (`sine`, `-sine`, and `cosine`) for the readout operations. By default, these weights assume a fixed angle. If variable angles are needed, subclass the [BaseReadoutPulse][quam.components.pulses.BaseReadoutPulse] class and override the `integration_weights_function()` to customize this behavior. + +This approach ensures your custom pulse configurations are both flexible and compatible with the broader QuAM framework. + + +## Pulses in QuAM and QUA + +The handling of pulses in QuAM and QUA presents fundamental differences in design philosophy and implementation, which can impact both usability and functionality. Understanding these differences is key for users who are transitioning to QuAM. Here's a comparison of how pulses are configured in each system: + +### QUA Configuration + +In the QUA configuration, pulses are decomposed into multiple components, such as `"waveforms"` and `"integration_weights"`. These components are defined separately and referenced by name within the `"pulses"` section of the configuration: + +- **Decomposition**: Each pulse is linked to a specific waveform and optionally, integration weights. This modular approach is more memory-efficient but may lead to fragmented configuration, where information about a single pulse is scattered across multiple sections. +- **Pulse Mapping**: The elements (channels) use an `operations` mapping to link a label (e.g., "X180") to a specific pulse setup. This system allows multiple channels to share a pulse, enhancing reusability but potentially complicating pulse modifications. +- **External Functions**: Typically, the lack of a parametrized representation means that external functions are often required to populate waveform entries. + +### QuAM Configuration + +Conversely, QuAM adopts a parametrized approach that encapsulates all pulse characteristics within a single class, aiming to simplify pulse definition and manipulation: + +- **Parametrized Representation**: Pulses in QuAM are instances of a parametrized class, where the type of pulse and its parameters (such as length and amplitude) are directly defined by the user. This simplifies the initial setup and modification of pulse configurations. +- **Waveform Generation**: These parameters are used to generate the waveform dynamically using the method `waveform_function()`. This approach integrates waveform generation within the pulse definition, streamlining the configuration process. +- **No built-in waveform reuse**: QuAM does not currently support sharing waveforms across pulses, as each pulse is defined independently. This can have implications for memory usage on the OPX. Reusing waveforms in QuAM is planned in a future release. + +### Conclusion + +Understanding the relationship between QuAM and QUA helps users navigate the choices available to them, balancing ease of use with the power and flexibility offered by direct QUA scripting. +By considering these aspects, users can better choose or adapt their system according to their specific needs and technical preferences. \ No newline at end of file diff --git a/docs/components/quam-root.md b/docs/components/quam-root.md new file mode 100644 index 00000000..e986b7c7 --- /dev/null +++ b/docs/components/quam-root.md @@ -0,0 +1,46 @@ +# QuAM Root + +The Quantum Abstract Machine (QuAM) utilizes a hierarchical data structure in which each QuAM component can contain nested child components. This architecture is centered around the root QuAM object, which acts as the top-level component and, uniquely, does not have a parent. + +## Overview + +The root QuAM object is structured as a subclass of [QuamRoot][quam.core.quam_classes.QuamRoot] module. For straightforward implementations, such as those described in the [Migrating to QuAM](/migrating-to-quam) section, the [BasicQuAM][quam.components.basic_quam.BasicQuAM] class typically suffices: + +```python +from quam.components import BasicQuAM + +machine = BasicQuAM() +machine.channels["qubit_xy"] = IQChannel(opx_output_I=("con1", 1), opx_output_Q=("con1", 2), ...) +``` + +## Customizing the Root Object + +For more complex setups requiring custom components, you can extend the QuAM root by subclassing and adding your specific components as attributes. This approach allows the QuAM root to be flexible and adaptable to various quantum machine configurations. + +### Example of a Custom QuAM Class + +```python title="custom_components/quam.py" +from typing import Dict +from quam.core import QuamRoot, quam_dataclass +from quam.components import Octave +from custom_components.qubit import Qubit + +@quam_dataclass +class QuAM(QuamRoot): + qubits: Dict[str, Qubit] + octaves: Dict[str, Octave] +``` + +You can then instantiate your customized QuAM object, adding components as required for your specific quantum machine setup: + +```python +from quam.components import Octave +from custom_components import QuAM, Qubit + +machine = QuAM( + qubits={"qubit1": Qubit(...), "qubit2": Qubit(...)}, + octaves={"octave1": Octave(...), "octave2": Octave(...)} +) +``` + +This structured approach ensures that all components of the quantum machine are integrated seamlessly, maintaining a clear and manageable codebase. diff --git a/docs/demonstration.md b/docs/demonstration.md index fe99cf32..f2b5a1ef 100644 --- a/docs/demonstration.md +++ b/docs/demonstration.md @@ -1,68 +1,138 @@ -# QuAM demonstration +# QuAM Demonstration -In this demonstration we will create a basic superconducting setup using standard components. -Note that QuAM is not specific to superconducting setups but is meant to serve any quantum platform. +## Introduction -The standard QuAM components can be imported using +Welcome to our QuAM tutorial! This guide will demonstrate setting up a basic superconducting quantum circuit with two transmon qubits and their resonators. We'll equip these qubits with control and readout pulses and generate a QUA configuration for interacting with quantum hardware. + +QuAM is not limited to any specific quantum hardware platform. It is designed to be adaptable and extensible for various quantum systems. You can customize components or expand the framework to add new functionalities as needed. For details on customization, visit [Custom QuAM Components](/components/custom-components). + +We will first demonstrate how to create a basic QuAM setup from scratch. This is typically done once at the beginning of a project. Then, we'll show how to modify the setup, save the changes, and generate a QUA configuration for running quantum programs. + +By the end of this tutorial, you'll know how to use QuAM effectively for setting up, controlling, and measuring quantum systems. + + +## Setting Up + +Start by importing the necessary components for a superconducting quantum circuit from QuAM's library: ```python from quam.components import * from quam.examples.superconducting_qubits import Transmon, QuAM ``` +As can be seen, we use transmon-specific components from `quam.examples.superconducting_qubits` to set up the quantum circuit. +Users are recommended to create their own custom components for specialized needs. -Since we're starting from scratch, we will have to instantiate all QuAM components. This has to be done once, after which we will generally save and load QuAM from a file. -To begin, we create the top-level QuAM object, which inherits from [quam.core.quam_classes.QuamRoot][]. Generally the user is encouraged to create a custom component for this. +## Initialization + +QuAM requires an initial setup where all components are instantiated. Create the root QuAM object, which acts as the top-level container for your quantum setup (see [QuAM Root Documentation](/components/quam-root) for details): -We will call our top-level object `machine`: ```python -machine = QuAM() +machine = QuAM() # (1) + ``` -So far, this object `machine` is empty, so we'll populate it with objects. -In this case, we will create two Transmon objects and fill them with contents. +1. The `QuAM` instance is called `machine` instead of `quam` to avoid conflicts with the statement `import quam` +Initially, `machine` is an empty container. You'll populate it with quantum circuit components, specifically Transmon qubits and associated resonators. -/// details | Autocomplete with IDEs - type: tip -Code editors with Python language support (e.g., VS Code, PyCharm) are very useful here because they explain what attributes each class has, what the type should be, and docstrings. This makes it a breeze to create a QuAM from scratch. -/// +## Populating the Machine +Define the number of qubits and initialize them, along with their channels and resonators: ```python num_qubits = 2 for idx in range(num_qubits): - # Create qubit components - transmon = Transmon( - id=idx, - xy=IQChannel( - frequency_converter_up=FrequencyConverter( - local_oscillator=LocalOscillator(power=10, frequency=6e9), - mixer=Mixer(), - ), - opx_output_I=("con1", 3 * idx + 3), - opx_output_Q=("con1", 3 * idx + 4), + # Create transmon qubit component + transmon = Transmon(id=idx) + machine.qubits[transmon.name] = transmon + + # Add xy drive line channel + transmon.xy = IQChannel( + opx_output_I=("con1", 3 * idx + 3), + opx_output_Q=("con1", 3 * idx + 4), + frequency_converter_up=FrequencyConverter( + mixer=Mixer(), + local_oscillator=LocalOscillator(power=10, frequency=6e9), ), - z=SingleChannel(opx_output=("con1", 3 * idx + 5)), + intermediate_frequency=100e6, ) - machine.qubits.append(transmon) - # Create resonator components - resonator = InOutIQChannel( + # Add transmon flux line channel + transmon.z = SingleChannel(opx_output=("con1", 3 * idx + 5)) + + # Add resonator channel + transmon.resonator = InOutIQChannel( + id=idx, + opx_output_I=("con1", 3 * idx + 1), + opx_output_Q=("con1", 3 * idx + 2), opx_input_I=("con1", 1), - opx_input_Q=("con1", 2), - opx_output_I=("con1", 1), - opx_output_Q=("con1", 2), - id=idx, + opx_input_Q=("con1", 2,), frequency_converter_up=FrequencyConverter( - local_oscillator=LocalOscillator(power=10, frequency=6e9), - mixer=Mixer() - ) + mixer=Mixer(), local_oscillator=LocalOscillator(power=10, frequency=6e9) + ), ) - machine.resonators.append(resonator) ``` -This example demonstrates that QuAM follows a tree structure: each component can have a parent and it can have children as attributes. -We can print a summary of QuAM using +/// details | Autocomplete with IDEs + type: tip +Code editors with Python language support (e.g., VS Code, PyCharm) are very useful here because they explain what attributes each class has, what the type should be, and docstrings. This makes it a breeze to create a QuAM from scratch. +/// + +This setup reflects QuAM's flexibility and the hierarchical structure of its component system where each component can be a parent or a child. + +## Adding a Pulse to a Qubit and its Resonator + +After configuring the qubits and resonators, you can further customize your setup by adding operational pulses. This example will show how to add a Gaussian pulse to the `xy` channel of a qubit and a `ReadoutPulse` to its resonator. + +### Defining and Attaching the Pulses + +#### Gaussian Pulse for Qubit Control + +Define a basic Gaussian pulse for qubit manipulation and attach it to the `xy` channel of the first qubit: + +```python +from quam.components.pulses import GaussianPulse + +# Create a Gaussian pulse +gaussian_pulse = GaussianPulse(length=20, amplitude=0.2, sigma=3) + +# Attach the pulse to the XY channel of the first qubit +machine.qubits["q0"].xy.operations["X90"] = gaussian_pulse +``` + +#### Readout Pulse for Qubit Resonator + +Similarly, define a `SquareReadoutPulse` (constant in readout amplitude) for the resonator associated with the same qubit to enable quantum state measurement: + +```python +from quam.components.pulses import SquareReadoutPulse + +# Create a Readout pulse +readout_pulse = SquareReadoutPulse(length=1000, amplitude=0.1) + +# Attach the pulse to the resonator of the first qubit +machine.qubits["q0"].resonator.operations["readout"] = readout_pulse +``` + +### Invoking the Pulses in a Function +```python +from qm.qua import program + +with program() as prog: + qubit = machine.qubits["q0"] + + # Apply the Gaussian pulse to the qubit + qubit.xy.play("X90") + + # Perform readout on the qubit + I, Q = qubit.resonator.measure("readout") +``` + + +## Overview of Configuration + +Display the current configuration of your QuAM setup: + ```python machine.print_summary() ``` @@ -70,12 +140,19 @@ machine.print_summary() /// details | `machine.print_summary()` output ```json QuAM: - mixers: QuamList = [] - qubits: QuamList: - 0: Transmon + qubits: QuamDict + q0: Transmon id: 0 xy: IQChannel - operations: QuamDict Empty + operations: QuamDict + X90: GaussianPulse + length: 20 + id: None + digital_marker: None + amplitude: 0.2 + sigma: 3 + axis_angle: None + subtracted: True id: None digital_outputs: QuamDict Empty opx_output_I: ('con1', 3) @@ -92,7 +169,7 @@ QuAM: correction_gain: 0 correction_phase: 0 gain: None - intermediate_frequency: 0.0 + intermediate_frequency: 100000000.0 z: SingleChannel operations: QuamDict Empty id: None @@ -102,8 +179,44 @@ QuAM: filter_iir_taps: None opx_output_offset: None intermediate_frequency: None - resonator: None - 1: Transmon + resonator: InOutIQChannel + operations: QuamDict + readout: SquareReadoutPulse + length: 1000 + id: None + digital_marker: "ON" + amplitude: 0.1 + axis_angle: None + threshold: None + rus_exit_threshold: None + integration_weights: None + integration_weights_angle: 0 + id: 0 + digital_outputs: QuamDict Empty + opx_output_I: ('con1', 1) + opx_output_Q: ('con1', 2) + opx_output_offset_I: None + opx_output_offset_Q: None + frequency_converter_up: FrequencyConverter + local_oscillator: LocalOscillator + frequency: 6000000000.0 + power: 10 + mixer: Mixer + local_oscillator_frequency: "#../local_oscillator/frequency" + intermediate_frequency: "#../../intermediate_frequency" + correction_gain: 0 + correction_phase: 0 + gain: None + intermediate_frequency: 0.0 + opx_input_I: ('con1', 1) + opx_input_Q: ('con1', 2) + time_of_flight: 24 + smearing: 0 + opx_input_offset_I: None + opx_input_offset_Q: None + input_gain: None + frequency_converter_down: None + q1: Transmon id: 1 xy: IQChannel operations: QuamDict Empty @@ -123,7 +236,7 @@ QuAM: correction_gain: 0 correction_phase: 0 gain: None - intermediate_frequency: 0.0 + intermediate_frequency: 100000000.0 z: SingleChannel operations: QuamDict Empty id: None @@ -133,71 +246,42 @@ QuAM: filter_iir_taps: None opx_output_offset: None intermediate_frequency: None - resonator: None - resonators: QuamList: - 0: InOutIQChannel - operations: QuamDict Empty - id: 0 - digital_outputs: QuamDict Empty - opx_output_I: ('con1', 1) - opx_output_Q: ('con1', 2) - opx_output_offset_I: None - opx_output_offset_Q: None - frequency_converter_up: FrequencyConverter - local_oscillator: LocalOscillator - frequency: 6000000000.0 - power: 10 - mixer: Mixer - local_oscillator_frequency: "#../local_oscillator/frequency" - intermediate_frequency: "#../../intermediate_frequency" - correction_gain: 0 - correction_phase: 0 - gain: None - intermediate_frequency: 0.0 - opx_input_I: ('con1', 1) - opx_input_Q: ('con1', 2) - time_of_flight: 24 - smearing: 0 - opx_input_offset_I: None - opx_input_offset_Q: None - input_gain: None - frequency_converter_down: None - 1: InOutIQChannel - operations: QuamDict Empty - id: 1 - digital_outputs: QuamDict Empty - opx_output_I: ('con1', 1) - opx_output_Q: ('con1', 2) - opx_output_offset_I: None - opx_output_offset_Q: None - frequency_converter_up: FrequencyConverter - local_oscillator: LocalOscillator - frequency: 6000000000.0 - power: 10 - mixer: Mixer - local_oscillator_frequency: "#../local_oscillator/frequency" - intermediate_frequency: "#../../intermediate_frequency" - correction_gain: 0 - correction_phase: 0 - gain: None - intermediate_frequency: 0.0 - opx_input_I: ('con1', 1) - opx_input_Q: ('con1', 2) - time_of_flight: 24 - smearing: 0 - opx_input_offset_I: None - opx_input_offset_Q: None - input_gain: None - frequency_converter_down: None - local_oscillators: QuamList = [] + resonator: InOutIQChannel + operations: QuamDict Empty + id: 1 + digital_outputs: QuamDict Empty + opx_output_I: ('con1', 4) + opx_output_Q: ('con1', 5) + opx_output_offset_I: None + opx_output_offset_Q: None + frequency_converter_up: FrequencyConverter + local_oscillator: LocalOscillator + frequency: 6000000000.0 + power: 10 + mixer: Mixer + local_oscillator_frequency: "#../local_oscillator/frequency" + intermediate_frequency: "#../../intermediate_frequency" + correction_gain: 0 + correction_phase: 0 + gain: None + intermediate_frequency: 0.0 + opx_input_I: ('con1', 1) + opx_input_Q: ('con1', 2) + time_of_flight: 24 + smearing: 0 + opx_input_offset_I: None + opx_input_offset_Q: None + input_gain: None + frequency_converter_down: None wiring: QuamDict Empty ``` /// +The output provides a detailed hierarchical view of the machine's configuration, illustrating the connectivity and settings of each component. -## Saving and loading QuAM +## Saving the QuAM Setup -Now that we have defined our QuAM structure, we can save its contents to a JSON file: +Save the current state of your QuAM setup to a file for later use or inspection: ```python machine.save("state.json") @@ -206,128 +290,122 @@ machine.save("state.json") /// details | state.json ```json { - "qubits": [ - { + "__class__": "quam.examples.superconducting_qubits.components.QuAM", + "qubits": { + "q0": { "id": 0, - "xy": { - "opx_output_I": [ - "con1", - 3 - ], - "opx_output_Q": [ - "con1", - 4 - ], + "resonator": { "frequency_converter_up": { - "local_oscillator": { - "frequency": 6000000000.0, - "power": 10 - }, - "mixer": {} - } + "__class__": "quam.components.hardware.FrequencyConverter", + "local_oscillator": {"frequency": 6000000000.0, "power": 10}, + "mixer": {}, + }, + "id": 0, + "operations": { + "readout": { + "__class__": "quam.components.pulses.SquareReadoutPulse", + "amplitude": 0.1, + "length": 1000, + } + }, + "opx_input_I": ["con1", 1], + "opx_input_Q": ["con1", 2], + "opx_output_I": ["con1", 1], + "opx_output_Q": ["con1", 2], }, - "z": { - "opx_output": [ - "con1", - 5 - ] - } - }, - { - "id": 1, "xy": { - "opx_output_I": [ - "con1", - 6 - ], - "opx_output_Q": [ - "con1", - 7 - ], "frequency_converter_up": { - "local_oscillator": { - "frequency": 6000000000.0, - "power": 10 - }, - "mixer": {} - } - }, - "z": { - "opx_output": [ - "con1", - 8 - ] - } - } - ], - "resonators": [ - { - "id": 0, - "opx_output_I": [ - "con1", - 1 - ], - "opx_output_Q": [ - "con1", - 2 - ], - "frequency_converter_up": { - "local_oscillator": { - "frequency": 6000000000.0, - "power": 10 + "__class__": "quam.components.hardware.FrequencyConverter", + "local_oscillator": {"frequency": 6000000000.0, "power": 10}, + "mixer": {}, + }, + "intermediate_frequency": 100000000.0, + "operations": { + "X90": { + "__class__": "quam.components.pulses.GaussianPulse", + "amplitude": 0.2, + "length": 20, + "sigma": 3, + } }, - "mixer": {} + "opx_output_I": ["con1", 3], + "opx_output_Q": ["con1", 4], }, - "opx_input_I": [ - "con1", - 1 - ], - "opx_input_Q": [ - "con1", - 2 - ] + "z": {"opx_output": ["con1", 5]}, }, - { + "q1": { "id": 1, - "opx_output_I": [ - "con1", - 1 - ], - "opx_output_Q": [ - "con1", - 2 - ], - "frequency_converter_up": { - "local_oscillator": { - "frequency": 6000000000.0, - "power": 10 + "resonator": { + "frequency_converter_up": { + "__class__": "quam.components.hardware.FrequencyConverter", + "local_oscillator": {"frequency": 6000000000.0, "power": 10}, + "mixer": {}, }, - "mixer": {} + "id": 1, + "opx_input_I": ["con1", 1], + "opx_input_Q": ["con1", 2], + "opx_output_I": ["con1", 4], + "opx_output_Q": ["con1", 5], }, - "opx_input_I": [ - "con1", - 1 - ], - "opx_input_Q": [ - "con1", - 2 - ] - } - ], - "__class__": "quam.components.superconducting_qubits.QuAM" + "xy": { + "frequency_converter_up": { + "__class__": "quam.components.hardware.FrequencyConverter", + "local_oscillator": {"frequency": 6000000000.0, "power": 10}, + "mixer": {}, + }, + "intermediate_frequency": 100000000.0, + "opx_output_I": ["con1", 6], + "opx_output_Q": ["con1", 7], + }, + "z": {"opx_output": ["con1", 8]}, + }, + }, } ``` /// -This JSON file is a serialised representation of QuAM. As a result, QuAM can also be loaded from this JSON file: +The contents of `state.json` will mirror the structure and settings of your QuAM machine. + +## Loading the Configuration + +To resume work with a previously configured setup: ```python loaded_machine = QuAM.load("state.json") ``` -## Generating the QUA configuration -We can also generate the QUA config from QuAM. This recursively calls `QuamComponent.apply_to_config()` on all QuAM components. +## Workflow + +Follow these steps for a typical execution flow in QuAM: + +1. **Initialize a New QuAM Setup**: Start by generating a new QuAM configuration for your quantum system as demonstrated earlier, and save this initial setup to a file. This step sets the baseline for your system's configuration. + +2. **Modify and Save**: Load the QuAM setup from the configuration file whenever you need to run new calibrations. After running your experiments and analyzing the results, you might need to adjust the configuration, such as updating pulse amplitudes based on your findings. + +**Example of Updating Pulse Amplitude**: Suppose a calibration determines a new optimal pulse amplitude. You would update the pulse amplitude in your QuAM setup and save the changes back to the configuration file. + +```python +# Load QuAM +machine = QuAM.load("state.json") + +# Run QUA program and analyse results to extract the optimal pulse amplitude +results = some_qua_program() +pulse_amplitude = amplitude_analysis_function(results) + +# Update the pulse amplitude for the relevant qubit +machine.qubits["q0"].xy.operations["X90"].amplitude = pulse_amplitude + +# Save the updated QuAM configuration +machine.save("state.json") +``` + +This workflow ensures your QuAM setup remains current with the latest experimental adjustments, allowing for iterative enhancements and refinements based on empirical data. + + +## Generating a QUA Configuration + +Generate a QUA configuration from the current QuAM setup. This is essential for interfacing with quantum hardware: ```python qua_config = machine.generate_config() @@ -336,241 +414,200 @@ qua_config = machine.generate_config() /// details | qua_config ```json { - "version": 1, - "controllers": { - "con1": { - "analog_outputs": { - "3": { - "offset": 0 - }, - "4": { - "offset": 0 - }, - "5": { - "offset": 0 - }, - "6": { - "offset": 0 - }, - "7": { - "offset": 0 + "controllers": { + "con1": { + "analog_inputs": {1: {"offset": 0.0}, 2: {"offset": 0.0}}, + "analog_outputs": { + 1: {"offset": 0.0}, + 2: {"offset": 0.0}, + 3: {"offset": 0.0}, + 4: {"offset": 0.0}, + 5: {"offset": 0.0}, + 6: {"offset": 0.0}, + 7: {"offset": 0.0}, + 8: {"offset": 0.0}, + }, + "digital_outputs": {}, + } + }, + "digital_waveforms": {"ON": {"samples": [[1, 0]]}}, + "elements": { + "IQ0": { + "intermediate_frequency": 0.0, + "mixInputs": { + "I": ("con1", 1), + "Q": ("con1", 2), + "lo_frequency": 6000000000.0, + "mixer": "IQ0.mixer", + }, + "operations": {"readout": "IQ0.readout.pulse"}, + "outputs": {"out1": ("con1", 1), "out2": ("con1", 2)}, + "smearing": 0, + "time_of_flight": 24, }, - "8": { - "offset": 0 + "IQ1": { + "intermediate_frequency": 0.0, + "mixInputs": { + "I": ("con1", 4), + "Q": ("con1", 5), + "lo_frequency": 6000000000.0, + "mixer": "IQ1.mixer", + }, + "operations": {}, + "outputs": {"out1": ("con1", 1), "out2": ("con1", 2)}, + "smearing": 0, + "time_of_flight": 24, }, - "1": { - "offset": 0 + "q0.xy": { + "intermediate_frequency": 100000000.0, + "mixInputs": { + "I": ("con1", 3), + "Q": ("con1", 4), + "lo_frequency": 6000000000.0, + "mixer": "q0.xy.mixer", + }, + "operations": {"X90": "q0.xy.X90.pulse"}, }, - "2": { - "offset": 0 - } - }, - "digital_outputs": {}, - "analog_inputs": { - "1": { - "offset": 0 + "q0.z": {"operations": {}, "singleInput": {"port": ("con1", 5)}}, + "q1.xy": { + "intermediate_frequency": 100000000.0, + "mixInputs": { + "I": ("con1", 6), + "Q": ("con1", 7), + "lo_frequency": 6000000000.0, + "mixer": "q1.xy.mixer", + }, + "operations": {}, }, - "2": { - "offset": 0 - } - } - } - }, - "elements": { - "q0.xy": { - "mixInputs": { - "I": [ - "con1", - 3 - ], - "Q": [ - "con1", - 4 - ], - "lo_frequency": 6000000000.0, - "mixer": "q0.xy.mixer" - }, - "intermediate_frequency": 0.0, - "operations": {} + "q1.z": {"operations": {}, "singleInput": {"port": ("con1", 8)}}, }, - "q0.z": { - "singleInput": { - "port": [ - "con1", - 5 - ] - }, - "operations": {} + "integration_weights": { + "IQ0.readout.iw1": {"cosine": [(1.0, 1000)], "sine": [(-0.0, 1000)]}, + "IQ0.readout.iw2": {"cosine": [(0.0, 1000)], "sine": [(1.0, 1000)]}, + "IQ0.readout.iw3": {"cosine": [(-0.0, 1000)], "sine": [(-1.0, 1000)]}, }, - "q1.xy": { - "mixInputs": { - "I": [ - "con1", - 6 - ], - "Q": [ - "con1", - 7 + "mixers": { + "IQ0.mixer": [ + { + "correction": [1.0, 0.0, 0.0, 1.0], + "intermediate_frequency": 0.0, + "lo_frequency": 6000000000.0, + } ], - "lo_frequency": 6000000000.0, - "mixer": "q1.xy.mixer" - }, - "intermediate_frequency": 0.0, - "operations": {} - }, - "q1.z": { - "singleInput": { - "port": [ - "con1", - 8 - ] - }, - "operations": {} - }, - "IQ0": { - "mixInputs": { - "I": [ - "con1", - 1 + "IQ1.mixer": [ + { + "correction": [1.0, 0.0, 0.0, 1.0], + "intermediate_frequency": 0.0, + "lo_frequency": 6000000000.0, + } ], - "Q": [ - "con1", - 2 + "q0.xy.mixer": [ + { + "correction": [1.0, 0.0, 0.0, 1.0], + "intermediate_frequency": 100000000.0, + "lo_frequency": 6000000000.0, + } ], - "lo_frequency": 6000000000.0, - "mixer": "IQ0.mixer" - }, - "intermediate_frequency": 0.0, - "operations": {}, - "outputs": { - "out1": [ - "con1", - 1 + "q1.xy.mixer": [ + { + "correction": [1.0, 0.0, 0.0, 1.0], + "intermediate_frequency": 100000000.0, + "lo_frequency": 6000000000.0, + } ], - "out2": [ - "con1", - 2 - ] - }, - "smearing": 0, - "time_of_flight": 24 }, - "IQ1": { - "mixInputs": { - "I": [ - "con1", - 1 - ], - "Q": [ - "con1", - 2 - ], - "lo_frequency": 6000000000.0, - "mixer": "IQ1.mixer" - }, - "intermediate_frequency": 0.0, - "operations": {}, - "outputs": { - "out1": [ - "con1", - 1 - ], - "out2": [ - "con1", - 2 - ] - }, - "smearing": 0, - "time_of_flight": 24 - } - }, - "pulses": { - "const_pulse": { - "operation": "control", - "length": 1000, - "waveforms": { - "I": "const_wf", - "Q": "zero_wf" - } - } - }, - "waveforms": { - "zero_wf": { - "type": "constant", - "sample": 0.0 + "oscillators": {}, + "pulses": { + "IQ0.readout.pulse": { + "digital_marker": "ON", + "integration_weights": { + "iw1": "IQ0.readout.iw1", + "iw2": "IQ0.readout.iw2", + "iw3": "IQ0.readout.iw3", + }, + "length": 1000, + "operation": "measurement", + "waveforms": {"I": "IQ0.readout.wf.I", "Q": "IQ0.readout.wf.Q"}, + }, + "const_pulse": { + "length": 1000, + "operation": "control", + "waveforms": {"I": "const_wf", "Q": "zero_wf"}, + }, + "q0.xy.X90.pulse": { + "length": 20, + "operation": "control", + "waveforms": {"I": "q0.xy.X90.wf.I", "Q": "q0.xy.X90.wf.Q"}, + }, + }, + "version": 1, + "waveforms": { + "IQ0.readout.wf.I": {"sample": 0.1, "type": "constant"}, + "IQ0.readout.wf.Q": {"sample": 0.0, "type": "constant"}, + "const_wf": {"sample": 0.1, "type": "constant"}, + "q0.xy.X90.wf.I": { + "samples": array( + [ + 0.0, + 0.0022836, + 0.00745838, + 0.01779789, + 0.03592509, + 0.06360149, + 0.09993812, + 0.14000065, + 0.17517038, + 0.19591242, + 0.19591242, + 0.17517038, + 0.14000065, + 0.09993812, + 0.06360149, + 0.03592509, + 0.01779789, + 0.00745838, + 0.0022836, + 0.0, + ] + ), + "type": "arbitrary", + }, + "q0.xy.X90.wf.Q": { + "samples": array( + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ] + ), + "type": "arbitrary", + }, + "zero_wf": {"sample": 0.0, "type": "constant"}, }, - "const_wf": { - "type": "constant", - "sample": 0.1 - } - }, - "digital_waveforms": { - "ON": { - "samples": [ - [ - 1, - 0 - ] - ] - } - }, - "integration_weights": {}, - "mixers": { - "q0.xy.mixer": [ - { - "intermediate_frequency": 0.0, - "lo_frequency": 6000000000.0, - "correction": [ - 1.0, - 0.0, - 0.0, - 1.0 - ] - } - ], - "q1.xy.mixer": [ - { - "intermediate_frequency": 0.0, - "lo_frequency": 6000000000.0, - "correction": [ - 1.0, - 0.0, - 0.0, - 1.0 - ] - } - ], - "IQ0.mixer": [ - { - "intermediate_frequency": 0.0, - "lo_frequency": 6000000000.0, - "correction": [ - 1.0, - 0.0, - 0.0, - 1.0 - ] - } - ], - "IQ1.mixer": [ - { - "intermediate_frequency": 0.0, - "lo_frequency": 6000000000.0, - "correction": [ - 1.0, - 0.0, - 0.0, - 1.0 - ] - } - ] - }, - "oscillators": {} } -``` + + ``` /// -This QUA config can then be used in QUA to open a Quantum Machine: +The resulting configuration is ready for use with QUA scripts to control quantum experiments. + ```python -qm = qmm.open_qm(qua_config) # opens a quantum machine with configuration +qm = qmm.open_qm(qua_config) # Open a quantum machine with the configuration ``` \ No newline at end of file diff --git a/docs/examples/QuAM features.ipynb b/docs/examples/QuAM features.ipynb index 348a6421..d7d33df3 100644 --- a/docs/examples/QuAM features.ipynb +++ b/docs/examples/QuAM features.ipynb @@ -4,14 +4,22 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# QuAM demonstration" + "# QuAM Demonstration" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2024-05-04 06:30:43,277 - qm - INFO - Starting session: 5fdcd5df-fd8e-4c5d-8708-16215135a260\n" + ] + } + ], "source": [ "import json\n", "from quam.components import *\n", @@ -28,7 +36,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Creating QuAM from scratch" + "## Creating QuAM from Scratch" ] }, { @@ -47,7 +55,7 @@ { "data": { "text/plain": [ - "QuAM(mixers=[], qubits=[], resonators=[], local_oscillators=[], wiring={})" + "QuAM(qubits={}, wiring={})" ] }, "execution_count": 2, @@ -73,14 +81,127 @@ "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "QuAM(mixers=[], qubits=[Transmon(id=0, xy=IQChannel(operations={}, id=None, digital_outputs={}, opx_output_I=('con1', 3), opx_output_Q=('con1', 4), opx_output_offset_I=None, opx_output_offset_Q=None, frequency_converter_up=FrequencyConverter(local_oscillator=LocalOscillator(frequency=6000000000.0, power=10), mixer=Mixer(local_oscillator_frequency=6000000000.0, intermediate_frequency=0.0, correction_gain=0, correction_phase=0), gain=None), intermediate_frequency=0.0), z=SingleChannel(operations={}, id=None, digital_outputs={}, opx_output=('con1', 5), filter_fir_taps=None, filter_iir_taps=None, opx_output_offset=None, intermediate_frequency=None), resonator=None), Transmon(id=1, xy=IQChannel(operations={}, id=None, digital_outputs={}, opx_output_I=('con1', 6), opx_output_Q=('con1', 7), opx_output_offset_I=None, opx_output_offset_Q=None, frequency_converter_up=FrequencyConverter(local_oscillator=LocalOscillator(frequency=6000000000.0, power=10), mixer=Mixer(local_oscillator_frequency=6000000000.0, intermediate_frequency=0.0, correction_gain=0, correction_phase=0), gain=None), intermediate_frequency=0.0), z=SingleChannel(operations={}, id=None, digital_outputs={}, opx_output=('con1', 8), filter_fir_taps=None, filter_iir_taps=None, opx_output_offset=None, intermediate_frequency=None), resonator=None)], resonators=[InOutIQChannel(operations={}, id=0, digital_outputs={}, opx_output_I=('con1', 1), opx_output_Q=('con1', 2), opx_output_offset_I=None, opx_output_offset_Q=None, frequency_converter_up=FrequencyConverter(local_oscillator=LocalOscillator(frequency=6000000000.0, power=10), mixer=Mixer(local_oscillator_frequency=6000000000.0, intermediate_frequency=0.0, correction_gain=0, correction_phase=0), gain=None), intermediate_frequency=0.0, opx_input_I=('con1', 1), opx_input_Q=('con1', 2), time_of_flight=24, smearing=0, opx_input_offset_I=None, opx_input_offset_Q=None, input_gain=None, frequency_converter_down=None), InOutIQChannel(operations={}, id=1, digital_outputs={}, opx_output_I=('con1', 1), opx_output_Q=('con1', 2), opx_output_offset_I=None, opx_output_offset_Q=None, frequency_converter_up=FrequencyConverter(local_oscillator=LocalOscillator(frequency=6000000000.0, power=10), mixer=Mixer(local_oscillator_frequency=6000000000.0, intermediate_frequency=0.0, correction_gain=0, correction_phase=0), gain=None), intermediate_frequency=0.0, opx_input_I=('con1', 1), opx_input_Q=('con1', 2), time_of_flight=24, smearing=0, opx_input_offset_I=None, opx_input_offset_Q=None, input_gain=None, frequency_converter_down=None)], local_oscillators=[], wiring={})" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "QuAM:\n", + " qubits: QuamDict\n", + " q0: Transmon\n", + " id: 0\n", + " xy: IQChannel\n", + " operations: QuamDict Empty\n", + " id: None\n", + " digital_outputs: QuamDict Empty\n", + " opx_output_I: ('con1', 3)\n", + " opx_output_Q: ('con1', 4)\n", + " opx_output_offset_I: None\n", + " opx_output_offset_Q: None\n", + " frequency_converter_up: FrequencyConverter\n", + " local_oscillator: LocalOscillator\n", + " frequency: 6000000000.0\n", + " power: 10\n", + " mixer: Mixer\n", + " local_oscillator_frequency: \"#../local_oscillator/frequency\"\n", + " intermediate_frequency: \"#../../intermediate_frequency\"\n", + " correction_gain: 0\n", + " correction_phase: 0\n", + " gain: None\n", + " intermediate_frequency: 100000000.0\n", + " z: SingleChannel\n", + " operations: QuamDict Empty\n", + " id: None\n", + " digital_outputs: QuamDict Empty\n", + " opx_output: ('con1', 5)\n", + " filter_fir_taps: None\n", + " filter_iir_taps: None\n", + " opx_output_offset: None\n", + " intermediate_frequency: None\n", + " resonator: InOutIQChannel\n", + " operations: QuamDict Empty\n", + " id: 0\n", + " digital_outputs: QuamDict Empty\n", + " opx_output_I: ('con1', 1)\n", + " opx_output_Q: ('con1', 2)\n", + " opx_output_offset_I: None\n", + " opx_output_offset_Q: None\n", + " frequency_converter_up: FrequencyConverter\n", + " local_oscillator: LocalOscillator\n", + " frequency: 6000000000.0\n", + " power: 10\n", + " mixer: Mixer\n", + " local_oscillator_frequency: \"#../local_oscillator/frequency\"\n", + " intermediate_frequency: \"#../../intermediate_frequency\"\n", + " correction_gain: 0\n", + " correction_phase: 0\n", + " gain: None\n", + " intermediate_frequency: 0.0\n", + " opx_input_I: ('con1', 1)\n", + " opx_input_Q: ('con1', 2)\n", + " time_of_flight: 24\n", + " smearing: 0\n", + " opx_input_offset_I: None\n", + " opx_input_offset_Q: None\n", + " input_gain: None\n", + " frequency_converter_down: None\n", + " q1: Transmon\n", + " id: 1\n", + " xy: IQChannel\n", + " operations: QuamDict Empty\n", + " id: None\n", + " digital_outputs: QuamDict Empty\n", + " opx_output_I: ('con1', 6)\n", + " opx_output_Q: ('con1', 7)\n", + " opx_output_offset_I: None\n", + " opx_output_offset_Q: None\n", + " frequency_converter_up: FrequencyConverter\n", + " local_oscillator: LocalOscillator\n", + " frequency: 6000000000.0\n", + " power: 10\n", + " mixer: Mixer\n", + " local_oscillator_frequency: \"#../local_oscillator/frequency\"\n", + " intermediate_frequency: \"#../../intermediate_frequency\"\n", + " correction_gain: 0\n", + " correction_phase: 0\n", + " gain: None\n", + " intermediate_frequency: 100000000.0\n", + " z: SingleChannel\n", + " operations: QuamDict Empty\n", + " id: None\n", + " digital_outputs: QuamDict Empty\n", + " opx_output: ('con1', 8)\n", + " filter_fir_taps: None\n", + " filter_iir_taps: None\n", + " opx_output_offset: None\n", + " intermediate_frequency: None\n", + " resonator: InOutIQChannel\n", + " operations: QuamDict Empty\n", + " id: 1\n", + " digital_outputs: QuamDict Empty\n", + " opx_output_I: ('con1', 4)\n", + " opx_output_Q: ('con1', 5)\n", + " opx_output_offset_I: None\n", + " opx_output_offset_Q: None\n", + " frequency_converter_up: FrequencyConverter\n", + " local_oscillator: LocalOscillator\n", + " frequency: 6000000000.0\n", + " power: 10\n", + " mixer: Mixer\n", + " local_oscillator_frequency: \"#../local_oscillator/frequency\"\n", + " intermediate_frequency: \"#../../intermediate_frequency\"\n", + " correction_gain: 0\n", + " correction_phase: 0\n", + " gain: None\n", + " intermediate_frequency: 0.0\n", + " opx_input_I: ('con1', 1)\n", + " opx_input_Q: ('con1', 2)\n", + " time_of_flight: 24\n", + " smearing: 0\n", + " opx_input_offset_I: None\n", + " opx_input_offset_Q: None\n", + " input_gain: None\n", + " frequency_converter_down: None\n", + " wiring: QuamDict Empty\n" + ] } ], "source": [ @@ -90,32 +211,30 @@ " transmon = Transmon(\n", " id=idx,\n", " xy=IQChannel(\n", + " opx_output_I=(\"con1\", 3 * idx + 3),\n", + " opx_output_Q=(\"con1\", 3 * idx + 4),\n", " frequency_converter_up=FrequencyConverter(\n", - " local_oscillator=LocalOscillator(power=10, frequency=6e9),\n", " mixer=Mixer(),\n", + " local_oscillator=LocalOscillator(power=10, frequency=6e9),\n", " ),\n", - " opx_output_I=(\"con1\", 3 * idx + 3),\n", - " opx_output_Q=(\"con1\", 3 * idx + 4),\n", + " intermediate_frequency=100e6,\n", " ),\n", " z=SingleChannel(opx_output=(\"con1\", 3 * idx + 5)),\n", " )\n", - " machine.qubits.append(transmon)\n", - "\n", - " # Create resonator components\n", - " resonator = InOutIQChannel(\n", + " machine.qubits[transmon.name] = transmon\n", + " readout_resonator = InOutIQChannel(\n", + " id=idx,\n", + " opx_output_I=(\"con1\", 3 * idx + 1),\n", + " opx_output_Q=(\"con1\", 3 * idx + 2),\n", " opx_input_I=(\"con1\", 1),\n", - " opx_input_Q=(\"con1\", 2),\n", - " opx_output_I=(\"con1\", 1),\n", - " opx_output_Q=(\"con1\", 2),\n", - " id=idx, \n", + " opx_input_Q=(\"con1\", 2,),\n", " frequency_converter_up=FrequencyConverter(\n", - " mixer=Mixer(),\n", - " local_oscillator=LocalOscillator(power=10, frequency=6e9),\n", + " mixer=Mixer(), local_oscillator=LocalOscillator(power=10, frequency=6e9)\n", " ),\n", " )\n", - " machine.resonators.append(resonator)\n", + " transmon.resonator = readout_resonator\n", "\n", - "machine" + "machine.print_summary()" ] }, { @@ -129,7 +248,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Saving and loading quam" + "## Saving and Loading QuAM" ] }, { @@ -141,7 +260,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -157,7 +276,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -175,7 +294,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Generating QUA config" + "## Generating QUA Configuration" ] }, { @@ -187,7 +306,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -197,7 +316,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -206,7 +325,7 @@ "PosixPath('output/basic/qua_config.json')" ] }, - "execution_count": 11, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -219,7 +338,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Quam using references" + "## QuAM Using References" ] }, { @@ -235,38 +354,165 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 8, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "QuAM(mixers=[], qubits=[Transmon(id=0, xy=IQChannel(operations={}, id=None, opx_output_I=('con1', 3), opx_output_Q=('con1', 4), opx_output_offset_I=0.0, opx_output_offset_Q=0.0, frequency_converter_up=FrequencyConverter(local_oscillator=LocalOscillator(frequency=6000000000.0, power=10), mixer=Mixer(local_oscillator_frequency=6000000000.0, intermediate_frequency=0.0, correction_gain=0, correction_phase=0), gain=None), intermediate_frequency=0.0), z=SingleChannel(operations={}, id=None, opx_output=('con1', 5), filter_fir_taps=None, filter_iir_taps=None, output_offset=0), resonator=None), Transmon(id=1, xy=IQChannel(operations={}, id=None, opx_output_I=('con1', 6), opx_output_Q=('con1', 7), opx_output_offset_I=0.0, opx_output_offset_Q=0.0, frequency_converter_up=FrequencyConverter(local_oscillator=LocalOscillator(frequency=6000000000.0, power=10), mixer=Mixer(local_oscillator_frequency=6000000000.0, intermediate_frequency=0.0, correction_gain=0, correction_phase=0), gain=None), intermediate_frequency=0.0), z=SingleChannel(operations={}, id=None, opx_output=('con1', 8), filter_fir_taps=None, filter_iir_taps=None, output_offset=0), resonator=None)], resonators=[InOutIQChannel(operations={}, id=0, opx_output_I=('con1', 1), opx_output_Q=('con1', 2), opx_output_offset_I=0.0, opx_output_offset_Q=0.0, frequency_converter_up=FrequencyConverter(local_oscillator=LocalOscillator(frequency=6000000000.0, power=10), mixer=Mixer(local_oscillator_frequency=6000000000.0, intermediate_frequency=0.0, correction_gain=0, correction_phase=0), gain=None), intermediate_frequency=0.0, opx_input_I=('con1', 1), opx_input_Q=('con1', 2), time_of_flight=24, smearing=0, input_offset_I=0.0, input_offset_Q=0.0, input_gain=None, frequency_converter_down=None), InOutIQChannel(operations={}, id=1, opx_output_I=('con1', 1), opx_output_Q=('con1', 2), opx_output_offset_I=0.0, opx_output_offset_Q=0.0, frequency_converter_up=FrequencyConverter(local_oscillator=LocalOscillator(frequency=6000000000.0, power=10), mixer=Mixer(local_oscillator_frequency=6000000000.0, intermediate_frequency=0.0, correction_gain=0, correction_phase=0), gain=None), intermediate_frequency=0.0, opx_input_I=('con1', 1), opx_input_Q=('con1', 2), time_of_flight=24, smearing=0, input_offset_I=0.0, input_offset_Q=0.0, input_gain=None, frequency_converter_down=None)], local_oscillators=[], wiring={'qubits': [{'opx_output_I': ('con1', 3), 'opx_output_Q': ('con1', 4), 'opx_output_Z': ('con1', 5)}, {'opx_output_I': ('con1', 6), 'opx_output_Q': ('con1', 7), 'opx_output_Z': ('con1', 8)}], 'resonator': {'opx_output_I': ('con1', 1), 'opx_output_Q': ('con1', 2), 'opx_input_I': ('con1', 1), 'opx_input_Q': ('con1', 2)}})" - ] - }, - "execution_count": 23, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "QuAM:\n", + " qubits: QuamDict\n", + " q0: Transmon\n", + " id: 0\n", + " xy: IQChannel\n", + " operations: QuamDict Empty\n", + " id: None\n", + " digital_outputs: QuamDict Empty\n", + " opx_output_I: \"#/wiring/qubits/q0/port_I\"\n", + " opx_output_Q: \"#/wiring/qubits/q0/port_Q\"\n", + " opx_output_offset_I: None\n", + " opx_output_offset_Q: None\n", + " frequency_converter_up: FrequencyConverter\n", + " local_oscillator: LocalOscillator\n", + " frequency: 6000000000.0\n", + " power: 10\n", + " mixer: Mixer\n", + " local_oscillator_frequency: \"#../local_oscillator/frequency\"\n", + " intermediate_frequency: \"#../../intermediate_frequency\"\n", + " correction_gain: 0\n", + " correction_phase: 0\n", + " gain: None\n", + " intermediate_frequency: 100000000.0\n", + " z: SingleChannel\n", + " operations: QuamDict Empty\n", + " id: None\n", + " digital_outputs: QuamDict Empty\n", + " opx_output: \"#/wiring/qubits/q0/port_Z\"\n", + " filter_fir_taps: None\n", + " filter_iir_taps: None\n", + " opx_output_offset: None\n", + " intermediate_frequency: None\n", + " resonator: InOutIQChannel\n", + " operations: QuamDict Empty\n", + " id: 0\n", + " digital_outputs: QuamDict Empty\n", + " opx_output_I: \"#/wiring/feedline/opx_output_I\"\n", + " opx_output_Q: \"#/wiring/feedline/opx_output_Q\"\n", + " opx_output_offset_I: None\n", + " opx_output_offset_Q: None\n", + " frequency_converter_up: FrequencyConverter\n", + " local_oscillator: LocalOscillator\n", + " frequency: 6000000000.0\n", + " power: 10\n", + " mixer: Mixer\n", + " local_oscillator_frequency: \"#../local_oscillator/frequency\"\n", + " intermediate_frequency: \"#../../intermediate_frequency\"\n", + " correction_gain: 0\n", + " correction_phase: 0\n", + " gain: None\n", + " intermediate_frequency: 0.0\n", + " opx_input_I: \"#/wiring/feedline/opx_input_I\"\n", + " opx_input_Q: \"#/wiring/feedline/opx_input_Q\"\n", + " time_of_flight: 24\n", + " smearing: 0\n", + " opx_input_offset_I: None\n", + " opx_input_offset_Q: None\n", + " input_gain: None\n", + " frequency_converter_down: None\n", + " q1: Transmon\n", + " id: 1\n", + " xy: IQChannel\n", + " operations: QuamDict Empty\n", + " id: None\n", + " digital_outputs: QuamDict Empty\n", + " opx_output_I: \"#/wiring/qubits/q1/port_I\"\n", + " opx_output_Q: \"#/wiring/qubits/q1/port_Q\"\n", + " opx_output_offset_I: None\n", + " opx_output_offset_Q: None\n", + " frequency_converter_up: FrequencyConverter\n", + " local_oscillator: LocalOscillator\n", + " frequency: 6000000000.0\n", + " power: 10\n", + " mixer: Mixer\n", + " local_oscillator_frequency: \"#../local_oscillator/frequency\"\n", + " intermediate_frequency: \"#../../intermediate_frequency\"\n", + " correction_gain: 0\n", + " correction_phase: 0\n", + " gain: None\n", + " intermediate_frequency: 100000000.0\n", + " z: SingleChannel\n", + " operations: QuamDict Empty\n", + " id: None\n", + " digital_outputs: QuamDict Empty\n", + " opx_output: \"#/wiring/qubits/q1/port_Z\"\n", + " filter_fir_taps: None\n", + " filter_iir_taps: None\n", + " opx_output_offset: None\n", + " intermediate_frequency: None\n", + " resonator: InOutIQChannel\n", + " operations: QuamDict Empty\n", + " id: 1\n", + " digital_outputs: QuamDict Empty\n", + " opx_output_I: \"#/wiring/feedline/opx_output_I\"\n", + " opx_output_Q: \"#/wiring/feedline/opx_output_Q\"\n", + " opx_output_offset_I: None\n", + " opx_output_offset_Q: None\n", + " frequency_converter_up: FrequencyConverter\n", + " local_oscillator: LocalOscillator\n", + " frequency: 6000000000.0\n", + " power: 10\n", + " mixer: Mixer\n", + " local_oscillator_frequency: \"#../local_oscillator/frequency\"\n", + " intermediate_frequency: \"#../../intermediate_frequency\"\n", + " correction_gain: 0\n", + " correction_phase: 0\n", + " gain: None\n", + " intermediate_frequency: 0.0\n", + " opx_input_I: \"#/wiring/feedline/opx_input_I\"\n", + " opx_input_Q: \"#/wiring/feedline/opx_input_Q\"\n", + " time_of_flight: 24\n", + " smearing: 0\n", + " opx_input_offset_I: None\n", + " opx_input_offset_Q: None\n", + " input_gain: None\n", + " frequency_converter_down: None\n", + " wiring: QuamDict\n", + " qubits: QuamDict\n", + " q0: QuamDict\n", + " port_I: ('con1', 3)\n", + " port_Q: ('con1', 4)\n", + " port_Z: ('con1', 5)\n", + " q1: QuamDict\n", + " port_I: ('con1', 6)\n", + " port_Q: ('con1', 7)\n", + " port_Z: ('con1', 8)\n", + " feedline: QuamDict\n", + " opx_output_I: ('con1', 1)\n", + " opx_output_Q: ('con1', 2)\n", + " opx_input_I: ('con1', 1)\n", + " opx_input_Q: ('con1', 2)\n" + ] } ], "source": [ "num_qubits = 2\n", "machine = QuAM()\n", "machine.wiring = {\n", - " \"qubits\": [\n", - " {\n", - " \"opx_output_I\": (\"con1\", 3 * k + 3), \n", - " \"opx_output_Q\": (\"con1\", 3 * k + 4), \n", - " \"opx_output_Z\": (\"con1\", 3 * k + 5)\n", + " \"qubits\": {\n", + " f\"q{idx}\": {\n", + " \"port_I\": (\"con1\", 3 * idx + 3),\n", + " \"port_Q\": (\"con1\", 3 * idx + 4),\n", + " \"port_Z\": (\"con1\", 3 * idx + 5),\n", " }\n", - " for k in range(num_qubits)\n", - " ],\n", - " \"resonator\": {\n", - " \"opx_output_I\": (\"con1\", 1), \n", - " \"opx_output_Q\": (\"con1\", 2), \n", - " \"opx_input_I\": (\"con1\", 1), \n", - " \"opx_input_Q\": (\"con1\", 2)\n", - " }\n", + " for idx in range(num_qubits)\n", + " },\n", + " \"feedline\": {\n", + " \"opx_output_I\": (\"con1\", 1),\n", + " \"opx_output_Q\": (\"con1\", 2),\n", + " \"opx_input_I\": (\"con1\", 1),\n", + " \"opx_input_Q\": (\"con1\", 2),\n", + " },\n", "}\n", "\n", "for idx in range(num_qubits):\n", @@ -274,32 +520,30 @@ " transmon = Transmon(\n", " id=idx,\n", " xy=IQChannel(\n", + " opx_output_I=f\"#/wiring/qubits/q{idx}/port_I\",\n", + " opx_output_Q=f\"#/wiring/qubits/q{idx}/port_Q\",\n", " frequency_converter_up=FrequencyConverter(\n", - " local_oscillator=LocalOscillator(power=10, frequency=6e9),\n", " mixer=Mixer(),\n", + " local_oscillator=LocalOscillator(power=10, frequency=6e9),\n", " ),\n", - " opx_output_I=f\"#/wiring/qubits/{idx}/opx_output_I\",\n", - " opx_output_Q=f\"#/wiring/qubits/{idx}/opx_output_Q\",\n", + " intermediate_frequency=100e6,\n", " ),\n", - " z=SingleChannel(opx_output=f\"#/wiring/qubits/{idx}/opx_output_Z\",),\n", + " z=SingleChannel(opx_output=f\"#/wiring/qubits/q{idx}/port_Z\"),\n", " )\n", - " machine.qubits.append(transmon)\n", - "\n", - " # Create resonator components\n", - " resonator = InOutIQChannel(\n", - " opx_input_I=\"#/wiring/resonator/opx_input_I\",\n", - " opx_input_Q=\"#/wiring/resonator/opx_input_Q\",\n", - " opx_output_I=\"#/wiring/resonator/opx_output_I\",\n", - " opx_output_Q=\"#/wiring/resonator/opx_output_Q\",\n", - " id=idx, \n", + " machine.qubits[transmon.name] = transmon\n", + " readout_resonator = InOutIQChannel(\n", + " id=idx,\n", + " opx_output_I=\"#/wiring/feedline/opx_output_I\",\n", + " opx_output_Q=\"#/wiring/feedline/opx_output_Q\",\n", + " opx_input_I=\"#/wiring/feedline/opx_input_I\",\n", + " opx_input_Q=\"#/wiring/feedline/opx_input_Q\",\n", " frequency_converter_up=FrequencyConverter(\n", - " mixer=Mixer(),\n", - " local_oscillator=LocalOscillator(power=10, frequency=6e9),\n", + " mixer=Mixer(), local_oscillator=LocalOscillator(power=10, frequency=6e9)\n", " ),\n", " )\n", - " machine.resonators.append(resonator)\n", + " transmon.resonator = readout_resonator\n", "\n", - "machine" + "machine.print_summary()" ] }, { @@ -311,7 +555,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -320,7 +564,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -332,7 +576,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Separating QuAM into multiple files" + "## Separating QuAM into Multiple Files" ] }, { @@ -347,7 +591,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -363,7 +607,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ diff --git a/docs/examples/output/basic/qua_config.json b/docs/examples/output/basic/qua_config.json index cb230a38..b093a293 100644 --- a/docs/examples/output/basic/qua_config.json +++ b/docs/examples/output/basic/qua_config.json @@ -10,21 +10,21 @@ "offset": 0.0 }, "5": { - "offset": 0 + "offset": 0.0 }, - "6": { + "1": { "offset": 0.0 }, - "7": { + "2": { "offset": 0.0 }, - "8": { - "offset": 0 + "6": { + "offset": 0.0 }, - "1": { + "7": { "offset": 0.0 }, - "2": { + "8": { "offset": 0.0 } }, @@ -41,6 +41,8 @@ }, "elements": { "q0.xy": { + "operations": {}, + "intermediate_frequency": 100000000.0, "mixInputs": { "I": [ "con1", @@ -52,87 +54,87 @@ ], "mixer": "q0.xy.mixer", "lo_frequency": 6000000000.0 - }, - "intermediate_frequency": 0.0, - "operations": {} + } }, "q0.z": { + "operations": {}, "singleInput": { "port": [ "con1", 5 ] - }, - "operations": {} + } }, - "q1.xy": { + "IQ0": { + "operations": {}, + "intermediate_frequency": 0.0, "mixInputs": { "I": [ "con1", - 6 + 1 ], "Q": [ "con1", - 7 + 2 ], - "mixer": "q1.xy.mixer", + "mixer": "IQ0.mixer", "lo_frequency": 6000000000.0 }, - "intermediate_frequency": 0.0, - "operations": {} - }, - "q1.z": { - "singleInput": { - "port": [ + "smearing": 0, + "time_of_flight": 24, + "outputs": { + "out1": [ "con1", - 8 + 1 + ], + "out2": [ + "con1", + 2 ] - }, - "operations": {} + } }, - "IQ0": { + "q1.xy": { + "operations": {}, + "intermediate_frequency": 100000000.0, "mixInputs": { "I": [ "con1", - 1 + 6 ], "Q": [ "con1", - 2 + 7 ], - "mixer": "IQ0.mixer", + "mixer": "q1.xy.mixer", "lo_frequency": 6000000000.0 - }, - "intermediate_frequency": 0.0, + } + }, + "q1.z": { "operations": {}, - "outputs": { - "out1": [ - "con1", - 1 - ], - "out2": [ + "singleInput": { + "port": [ "con1", - 2 + 8 ] - }, - "smearing": 0, - "time_of_flight": 24 + } }, "IQ1": { + "operations": {}, + "intermediate_frequency": 0.0, "mixInputs": { "I": [ "con1", - 1 + 4 ], "Q": [ "con1", - 2 + 5 ], "mixer": "IQ1.mixer", "lo_frequency": 6000000000.0 }, - "intermediate_frequency": 0.0, - "operations": {}, + "smearing": 0, + "time_of_flight": 24, "outputs": { "out1": [ "con1", @@ -142,9 +144,7 @@ "con1", 2 ] - }, - "smearing": 0, - "time_of_flight": 24 + } } }, "pulses": { @@ -181,7 +181,7 @@ "mixers": { "q0.xy.mixer": [ { - "intermediate_frequency": 0.0, + "intermediate_frequency": 100000000.0, "lo_frequency": 6000000000.0, "correction": [ 1.0, @@ -191,7 +191,7 @@ ] } ], - "q1.xy.mixer": [ + "IQ0.mixer": [ { "intermediate_frequency": 0.0, "lo_frequency": 6000000000.0, @@ -203,9 +203,9 @@ ] } ], - "IQ0.mixer": [ + "q1.xy.mixer": [ { - "intermediate_frequency": 0.0, + "intermediate_frequency": 100000000.0, "lo_frequency": 6000000000.0, "correction": [ 1.0, diff --git a/docs/examples/output/basic/state.json b/docs/examples/output/basic/state.json index e11439ca..eda52343 100644 --- a/docs/examples/output/basic/state.json +++ b/docs/examples/output/basic/state.json @@ -1,6 +1,6 @@ { - "qubits": [ - { + "qubits": { + "q0": { "id": 0, "xy": { "opx_output_I": [ @@ -16,17 +16,46 @@ "frequency": 6000000000.0, "power": 10 }, - "mixer": {} - } + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" + }, + "intermediate_frequency": 100000000.0 }, "z": { "opx_output": [ "con1", 5 ] + }, + "resonator": { + "id": 0, + "opx_output_I": [ + "con1", + 1 + ], + "opx_output_Q": [ + "con1", + 2 + ], + "frequency_converter_up": { + "local_oscillator": { + "frequency": 6000000000.0, + "power": 10 + }, + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" + }, + "opx_input_I": [ + "con1", + 1 + ], + "opx_input_Q": [ + "con1", + 2 + ] } }, - { + "q1": { "id": 1, "xy": { "opx_output_I": [ @@ -42,70 +71,45 @@ "frequency": 6000000000.0, "power": 10 }, - "mixer": {} - } + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" + }, + "intermediate_frequency": 100000000.0 }, "z": { "opx_output": [ "con1", 8 ] - } - } - ], - "resonators": [ - { - "id": 0, - "opx_output_I": [ - "con1", - 1 - ], - "opx_output_Q": [ - "con1", - 2 - ], - "frequency_converter_up": { - "local_oscillator": { - "frequency": 6000000000.0, - "power": 10 - }, - "mixer": {} }, - "opx_input_I": [ - "con1", - 1 - ], - "opx_input_Q": [ - "con1", - 2 - ] - }, - { - "id": 1, - "opx_output_I": [ - "con1", - 1 - ], - "opx_output_Q": [ - "con1", - 2 - ], - "frequency_converter_up": { - "local_oscillator": { - "frequency": 6000000000.0, - "power": 10 + "resonator": { + "id": 1, + "opx_output_I": [ + "con1", + 4 + ], + "opx_output_Q": [ + "con1", + 5 + ], + "frequency_converter_up": { + "local_oscillator": { + "frequency": 6000000000.0, + "power": 10 + }, + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" }, - "mixer": {} - }, - "opx_input_I": [ - "con1", - 1 - ], - "opx_input_Q": [ - "con1", - 2 - ] + "opx_input_I": [ + "con1", + 1 + ], + "opx_input_Q": [ + "con1", + 2 + ] + } } - ], - "__class__": "quam.components.superconducting_qubits.QuAM" + }, + "__class__": "quam.examples.superconducting_qubits.components.QuAM" } \ No newline at end of file diff --git a/docs/examples/output/referenced/qua_config.json b/docs/examples/output/referenced/qua_config.json index cb230a38..6c8ff1ce 100644 --- a/docs/examples/output/referenced/qua_config.json +++ b/docs/examples/output/referenced/qua_config.json @@ -10,21 +10,21 @@ "offset": 0.0 }, "5": { - "offset": 0 + "offset": 0.0 }, - "6": { + "1": { "offset": 0.0 }, - "7": { + "2": { "offset": 0.0 }, - "8": { - "offset": 0 + "6": { + "offset": 0.0 }, - "1": { + "7": { "offset": 0.0 }, - "2": { + "8": { "offset": 0.0 } }, @@ -41,6 +41,8 @@ }, "elements": { "q0.xy": { + "operations": {}, + "intermediate_frequency": 100000000.0, "mixInputs": { "I": [ "con1", @@ -52,73 +54,73 @@ ], "mixer": "q0.xy.mixer", "lo_frequency": 6000000000.0 - }, - "intermediate_frequency": 0.0, - "operations": {} + } }, "q0.z": { + "operations": {}, "singleInput": { "port": [ "con1", 5 ] - }, - "operations": {} + } }, - "q1.xy": { + "IQ0": { + "operations": {}, + "intermediate_frequency": 0.0, "mixInputs": { "I": [ "con1", - 6 + 1 ], "Q": [ "con1", - 7 + 2 ], - "mixer": "q1.xy.mixer", + "mixer": "IQ0.mixer", "lo_frequency": 6000000000.0 }, - "intermediate_frequency": 0.0, - "operations": {} - }, - "q1.z": { - "singleInput": { - "port": [ + "smearing": 0, + "time_of_flight": 24, + "outputs": { + "out1": [ "con1", - 8 + 1 + ], + "out2": [ + "con1", + 2 ] - }, - "operations": {} + } }, - "IQ0": { + "q1.xy": { + "operations": {}, + "intermediate_frequency": 100000000.0, "mixInputs": { "I": [ "con1", - 1 + 6 ], "Q": [ "con1", - 2 + 7 ], - "mixer": "IQ0.mixer", + "mixer": "q1.xy.mixer", "lo_frequency": 6000000000.0 - }, - "intermediate_frequency": 0.0, + } + }, + "q1.z": { "operations": {}, - "outputs": { - "out1": [ - "con1", - 1 - ], - "out2": [ + "singleInput": { + "port": [ "con1", - 2 + 8 ] - }, - "smearing": 0, - "time_of_flight": 24 + } }, "IQ1": { + "operations": {}, + "intermediate_frequency": 0.0, "mixInputs": { "I": [ "con1", @@ -131,8 +133,8 @@ "mixer": "IQ1.mixer", "lo_frequency": 6000000000.0 }, - "intermediate_frequency": 0.0, - "operations": {}, + "smearing": 0, + "time_of_flight": 24, "outputs": { "out1": [ "con1", @@ -142,9 +144,7 @@ "con1", 2 ] - }, - "smearing": 0, - "time_of_flight": 24 + } } }, "pulses": { @@ -181,7 +181,7 @@ "mixers": { "q0.xy.mixer": [ { - "intermediate_frequency": 0.0, + "intermediate_frequency": 100000000.0, "lo_frequency": 6000000000.0, "correction": [ 1.0, @@ -191,7 +191,7 @@ ] } ], - "q1.xy.mixer": [ + "IQ0.mixer": [ { "intermediate_frequency": 0.0, "lo_frequency": 6000000000.0, @@ -203,9 +203,9 @@ ] } ], - "IQ0.mixer": [ + "q1.xy.mixer": [ { - "intermediate_frequency": 0.0, + "intermediate_frequency": 100000000.0, "lo_frequency": 6000000000.0, "correction": [ 1.0, diff --git a/docs/examples/output/referenced/state.json b/docs/examples/output/referenced/state.json index 3cfc79e5..40777dec 100644 --- a/docs/examples/output/referenced/state.json +++ b/docs/examples/output/referenced/state.json @@ -1,102 +1,106 @@ { - "qubits": [ - { + "qubits": { + "q0": { "id": 0, "xy": { - "opx_output_I": "#/wiring/qubits/0/opx_output_I", - "opx_output_Q": "#/wiring/qubits/0/opx_output_Q", + "opx_output_I": "#/wiring/qubits/q0/port_I", + "opx_output_Q": "#/wiring/qubits/q0/port_Q", "frequency_converter_up": { "local_oscillator": { "frequency": 6000000000.0, "power": 10 }, - "mixer": {} - } + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" + }, + "intermediate_frequency": 100000000.0 }, "z": { - "opx_output": "#/wiring/qubits/0/opx_output_Z" + "opx_output": "#/wiring/qubits/q0/port_Z" + }, + "resonator": { + "id": 0, + "opx_output_I": "#/wiring/feedline/opx_output_I", + "opx_output_Q": "#/wiring/feedline/opx_output_Q", + "frequency_converter_up": { + "local_oscillator": { + "frequency": 6000000000.0, + "power": 10 + }, + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" + }, + "opx_input_I": "#/wiring/feedline/opx_input_I", + "opx_input_Q": "#/wiring/feedline/opx_input_Q" } }, - { + "q1": { "id": 1, "xy": { - "opx_output_I": "#/wiring/qubits/1/opx_output_I", - "opx_output_Q": "#/wiring/qubits/1/opx_output_Q", + "opx_output_I": "#/wiring/qubits/q1/port_I", + "opx_output_Q": "#/wiring/qubits/q1/port_Q", "frequency_converter_up": { "local_oscillator": { "frequency": 6000000000.0, "power": 10 }, - "mixer": {} - } + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" + }, + "intermediate_frequency": 100000000.0 }, "z": { - "opx_output": "#/wiring/qubits/1/opx_output_Z" - } - } - ], - "resonators": [ - { - "id": 0, - "opx_output_I": "#/wiring/resonator/opx_output_I", - "opx_output_Q": "#/wiring/resonator/opx_output_Q", - "frequency_converter_up": { - "local_oscillator": { - "frequency": 6000000000.0, - "power": 10 - }, - "mixer": {} + "opx_output": "#/wiring/qubits/q1/port_Z" }, - "opx_input_I": "#/wiring/resonator/opx_input_I", - "opx_input_Q": "#/wiring/resonator/opx_input_Q" - }, - { - "id": 1, - "opx_output_I": "#/wiring/resonator/opx_output_I", - "opx_output_Q": "#/wiring/resonator/opx_output_Q", - "frequency_converter_up": { - "local_oscillator": { - "frequency": 6000000000.0, - "power": 10 + "resonator": { + "id": 1, + "opx_output_I": "#/wiring/feedline/opx_output_I", + "opx_output_Q": "#/wiring/feedline/opx_output_Q", + "frequency_converter_up": { + "local_oscillator": { + "frequency": 6000000000.0, + "power": 10 + }, + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" }, - "mixer": {} - }, - "opx_input_I": "#/wiring/resonator/opx_input_I", - "opx_input_Q": "#/wiring/resonator/opx_input_Q" + "opx_input_I": "#/wiring/feedline/opx_input_I", + "opx_input_Q": "#/wiring/feedline/opx_input_Q" + } } - ], + }, "wiring": { - "qubits": [ - { - "opx_output_I": [ + "qubits": { + "q0": { + "port_I": [ "con1", 3 ], - "opx_output_Q": [ + "port_Q": [ "con1", 4 ], - "opx_output_Z": [ + "port_Z": [ "con1", 5 ] }, - { - "opx_output_I": [ + "q1": { + "port_I": [ "con1", 6 ], - "opx_output_Q": [ + "port_Q": [ "con1", 7 ], - "opx_output_Z": [ + "port_Z": [ "con1", 8 ] } - ], - "resonator": { + }, + "feedline": { "opx_output_I": [ "con1", 1 @@ -115,5 +119,5 @@ ] } }, - "__class__": "quam.components.superconducting_qubits.QuAM" + "__class__": "quam.examples.superconducting_qubits.components.QuAM" } \ No newline at end of file diff --git a/docs/examples/output/referenced_multifile/quam/state.json b/docs/examples/output/referenced_multifile/quam/state.json index ccaef35b..91dbf815 100644 --- a/docs/examples/output/referenced_multifile/quam/state.json +++ b/docs/examples/output/referenced_multifile/quam/state.json @@ -1,69 +1,73 @@ { - "qubits": [ - { + "qubits": { + "q0": { "id": 0, "xy": { - "opx_output_I": "#/wiring/qubits/0/opx_output_I", - "opx_output_Q": "#/wiring/qubits/0/opx_output_Q", + "opx_output_I": "#/wiring/qubits/q0/port_I", + "opx_output_Q": "#/wiring/qubits/q0/port_Q", "frequency_converter_up": { "local_oscillator": { "frequency": 6000000000.0, "power": 10 }, - "mixer": {} - } + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" + }, + "intermediate_frequency": 100000000.0 }, "z": { - "opx_output": "#/wiring/qubits/0/opx_output_Z" + "opx_output": "#/wiring/qubits/q0/port_Z" + }, + "resonator": { + "id": 0, + "opx_output_I": "#/wiring/feedline/opx_output_I", + "opx_output_Q": "#/wiring/feedline/opx_output_Q", + "frequency_converter_up": { + "local_oscillator": { + "frequency": 6000000000.0, + "power": 10 + }, + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" + }, + "opx_input_I": "#/wiring/feedline/opx_input_I", + "opx_input_Q": "#/wiring/feedline/opx_input_Q" } }, - { + "q1": { "id": 1, "xy": { - "opx_output_I": "#/wiring/qubits/1/opx_output_I", - "opx_output_Q": "#/wiring/qubits/1/opx_output_Q", + "opx_output_I": "#/wiring/qubits/q1/port_I", + "opx_output_Q": "#/wiring/qubits/q1/port_Q", "frequency_converter_up": { "local_oscillator": { "frequency": 6000000000.0, "power": 10 }, - "mixer": {} - } + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" + }, + "intermediate_frequency": 100000000.0 }, "z": { - "opx_output": "#/wiring/qubits/1/opx_output_Z" - } - } - ], - "resonators": [ - { - "id": 0, - "opx_output_I": "#/wiring/resonator/opx_output_I", - "opx_output_Q": "#/wiring/resonator/opx_output_Q", - "frequency_converter_up": { - "local_oscillator": { - "frequency": 6000000000.0, - "power": 10 - }, - "mixer": {} + "opx_output": "#/wiring/qubits/q1/port_Z" }, - "opx_input_I": "#/wiring/resonator/opx_input_I", - "opx_input_Q": "#/wiring/resonator/opx_input_Q" - }, - { - "id": 1, - "opx_output_I": "#/wiring/resonator/opx_output_I", - "opx_output_Q": "#/wiring/resonator/opx_output_Q", - "frequency_converter_up": { - "local_oscillator": { - "frequency": 6000000000.0, - "power": 10 + "resonator": { + "id": 1, + "opx_output_I": "#/wiring/feedline/opx_output_I", + "opx_output_Q": "#/wiring/feedline/opx_output_Q", + "frequency_converter_up": { + "local_oscillator": { + "frequency": 6000000000.0, + "power": 10 + }, + "mixer": {}, + "__class__": "quam.components.hardware.FrequencyConverter" }, - "mixer": {} - }, - "opx_input_I": "#/wiring/resonator/opx_input_I", - "opx_input_Q": "#/wiring/resonator/opx_input_Q" + "opx_input_I": "#/wiring/feedline/opx_input_I", + "opx_input_Q": "#/wiring/feedline/opx_input_Q" + } } - ], - "__class__": "quam.components.superconducting_qubits.QuAM" + }, + "__class__": "quam.examples.superconducting_qubits.components.QuAM" } \ No newline at end of file diff --git a/docs/examples/output/referenced_multifile/quam/wiring.json b/docs/examples/output/referenced_multifile/quam/wiring.json index a39c7ed4..e6bc3ce7 100644 --- a/docs/examples/output/referenced_multifile/quam/wiring.json +++ b/docs/examples/output/referenced_multifile/quam/wiring.json @@ -1,36 +1,36 @@ { "wiring": { - "qubits": [ - { - "opx_output_I": [ + "qubits": { + "q0": { + "port_I": [ "con1", 3 ], - "opx_output_Q": [ + "port_Q": [ "con1", 4 ], - "opx_output_Z": [ + "port_Z": [ "con1", 5 ] }, - { - "opx_output_I": [ + "q1": { + "port_I": [ "con1", 6 ], - "opx_output_Q": [ + "port_Q": [ "con1", 7 ], - "opx_output_Z": [ + "port_Z": [ "con1", 8 ] } - ], - "resonator": { + }, + "feedline": { "opx_output_I": [ "con1", 1 diff --git a/docs/features/index.md b/docs/features/index.md new file mode 100644 index 00000000..1d2977d9 --- /dev/null +++ b/docs/features/index.md @@ -0,0 +1,10 @@ +# QuAM Features + +The features section of the Quantum Abstract Machine (QuAM) documentation highlights the unique capabilities and functionalities that enhance the usability and performance of the QuAM framework. Here, you will find detailed explanations and examples of how to leverage these features in your quantum projects. + +## QuAM Referencing +QuAM referencing is a sophisticated feature designed to streamline the management and modification of quantum configurations. It allows users to define relationships and dependencies between various components within a QuAM setup, facilitating easier updates and scalability. + +- **[QuAM Referencing Documentation](quam-references.md)**: Learn about the syntax and usage of QuAM’s powerful referencing system. This feature simplifies the process of modifying and scaling quantum experiments by allowing users to reference previously defined elements, parameters, and configurations, thus avoiding redundancy and reducing the potential for errors. + +This section of the documentation provides insights into the foundational features that make QuAM a robust and adaptable framework for quantum computing. Explore the detailed documentation to fully utilize these features in your work. diff --git a/docs/quam-references.md b/docs/features/quam-references.md similarity index 51% rename from docs/quam-references.md rename to docs/features/quam-references.md index 162e1ca9..c28c7f9e 100644 --- a/docs/quam-references.md +++ b/docs/features/quam-references.md @@ -1,6 +1,6 @@ -# Component referencing +# Referencing Between Components -## QuAM tree-structure +## QuAM Tree Structure QuAM follows a tree structure, meaning that each QuAM component can have a parent component and it can have children. The top-level object is always an instance of QuAMRoot, e.g. @@ -18,31 +18,39 @@ machine = QuAM() Next, we can add a qubit as a component: ```python -qubit = superconducting_qubits.Transmon(xy=IQChannel()) +qubit = superconducting_qubits.Transmon(xy=IQChannel(opx_output_I=("con1", 1), opx_output_Q=("con1", 2")) machine.qubit = qubit assert qubit.parent == machine ``` -However, situations often arise where a component needs access to another part of QuAM that is not directly one of its children. To accomodate this, we introduce the concept of references. +One of the rules in QuAM is that a component can only have one parent. This is enforced by the `parent` attribute, which is set when a component is added to another component. +As a result, the following raises an error: -## QuAM references +```python +channel = IQChannel(opx_output_I=("con1", 1), opx_output_Q=("con1", 2") +qubit1 = superconducting_qubits.Transmon(xy=channel) +qubit2 = superconducting_qubits.Transmon(xy=channel) # Raises ValueError +``` +additionally, situations often arise where a component needs access to another part of QuAM that is not directly one of its children. +To accomodate both of these situations, we introduce the concept of references. + +## QuAM References A reference in QuAM is a way for a component's attribute to be a reference to another part of QuAM. An example is shown here ```python @quam_dataclass class Component(QuamComponent): + a: int + b: int - -component = Component() -component.a = 42 -component.b = "#./a" +component = Component(a=42, b="#./a") print(component.b) # Prints 42 ``` -As can be seen, the Quam component attribute `component.b` was set to a reference, i.e. a string starting with `"#"`. This reference indicates that when the component is retrieved, e.g. through the `print()` statement, it should instead return the value of its reference. +As can be seen, the QuAM component attribute `component.b` was set to a reference, i.e. a string starting with `"#"`. This reference indicates that when the component is retrieved, e.g. through the `print()` statement, it should instead return the value of its reference. -Quam references follow the JSON pointer syntax (For a description see https://datatracker.ietf.org/doc/html/rfc6901), but further allow for relative references, i.e. references w.r.t the current Quam component. We will next describe the three types of references. +QuAM references follow the JSON reference syntax (For a description see [https://json-spec.readthedocs.io/reference.html](https://json-spec.readthedocs.io/reference.html)), but further allow for relative references, i.e. references w.r.t the current QuAM component. We will next describe the three types of references. -### Absolute references +### Absolute References Absolute references always start with `"#/"`, e.g. `"#/absolute/path/to/value`. They are references from the top-level QuAM object which inherits from `QuamRoot` For example: @@ -53,7 +61,7 @@ machine.qubit = Transmon(frequency="#/frequency") print(machine.qubit.frequency) # Prints 6e9 ``` -### Relative references +### Relative References Relative references start with `"#./"`, e.g. `"#./relative/path/to/value` These are references with respect to the current QuAM component. An example was given above, and is reiterated here: @@ -61,35 +69,40 @@ An example was given above, and is reiterated here: ```python @quam_dataclass class Component(QuamComponent): + a: int + b: int - -component = Component() -component.a = 42 -component.b = "#./a" +component = Component(a=42, b="#./a") print(component.b) # Prints 42 ``` -### Relative parent references +### Relative Parent References Relative parent references start with `"#../"`, e.g. `"#../relative/path/from/parent/to/value` These are references with respect to the parent of the current QuAM component. -Note that the parent -An example was given above, and is reiterated here: + +To illustrate relative parent references, we modify `Component` to allow for a subcomponent: ```python @quam_dataclass class Component(QuamComponent): - + sub_component: "Component" = None + a: int = None + b: int = None -component = Component() -component.a = 42 -component.b = "#./a" -print(component.b) # Prints 42 +component = Component(a=42) +component.subcomponent = Component(a="#../a") +print(component.subcomponent.a) # Prints 42 ``` -## Additional notes on references +As can be seen in this example, `component.subcomponent.a = "#../a"` is a relative parent reference, which means that `component.subcomponent.a` should be the same as `component.a`. + +Parent references can also be stacked, e.g. `"#../../a"` would be a reference to the grandparent of the current component. + + +## Additional Notes on References -### Directly overwriting references is not allowed -Since Quam references behave like regular attributes, the user might accidentally overwrite a reference without realizing it. To prohibit this, it is not possible to directly overwrite a reference: +### Directly Overwriting References is not Allowed +Since QuAM references behave like regular attributes, the user might accidentally overwrite a reference without realizing it. To prohibit this, it is not possible to directly overwrite a reference: ```python component = Component() diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py index e29bde0a..932de154 100644 --- a/docs/gen_ref_pages.py +++ b/docs/gen_ref_pages.py @@ -7,7 +7,7 @@ for path in sorted(Path("quam").rglob("*.py")): # module_path = path.relative_to("quam").with_suffix("") # doc_path = path.relative_to("quam").with_suffix(".md") # - full_doc_path = Path("reference", doc_path) # + full_doc_path = Path("API_references", doc_path) # parts = list(module_path.parts) parts = ["quam"] + parts diff --git a/docs/index.md b/docs/index.md index 2872ed6d..3c7029ef 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,13 +1,57 @@ # Welcome to QuAM -Welcome to the documentation for QuAM (Quantum Abstraction Machine). -QuAM is a software framework that provides an abstraction layer for the [QUA programming language](https://docs.quantum-machines.co/). -Whereas QUA, and especially the QUA configuration, approaches quantum control from a generic hardware perspective, QuAM allows the user to interact with the Quantum Orchestration Platform from the physicist's perspective. -It does so by providing a framework that allows the creation of abstraction layers, such that instead of channels and waveforms, users can interact with qubits and qubit operations. - -## Key features -- Standard set of QuAM components (e.g. Mixer, IQChannel) that allow you to digitally represent your quantum setup. -- Automated generation of the QUA configuration from QuAM components -- Framework for easily extending QuAM with custom classes. - This allows you to build abstraction layers to manage even the most complex quantum setups. -- Saving / loading your QuAM state. \ No newline at end of file +**Empowering Quantum Innovation with Enhanced Abstraction** + +Welcome to the official documentation for the Quantum Abstract Machine (QuAM), a software framework designed to enhance the user experience of quantum computing by providing an abstraction layer over the [QUA programming language](https://docs.quantum-machines.co/). QuAM allows users, particularly physicists, to interact with the Quantum Orchestration Platform more intuitively, shifting from a hardware-centric to a physicist-friendly approach. + +## What is QuAM? +QuAM stands out by transforming the way quantum control is perceived and implemented. While QUA and its configurations tackle quantum control from a generic hardware perspective, QuAM introduces a higher level of abstraction. It allows you to think in terms of qubits and quantum operations rather than just channels and waveforms, aligning more closely with the thought processes of physicists. + +## Why Choose QuAM? + +QuAM is not just a tool but a gateway to streamlined and efficient quantum computing: +
+ +- **Component-Based Setup:** Utilize a standard set of QuAM components like [Mixers][quam.components.hardware.Mixer] and [IQChannels][quam.components.channels.IQChannel] to digitally represent and manipulate your quantum environment. +- **Automated Configuration:** Automatically generate the necessary QUA configuration from your QuAM setup, simplifying the transition from design to deployment. +- **Extensibility:** Easily extend QuAM with [custom classes](components/custom-components.md) to accommodate complex quantum setups, providing flexibility and power in your quantum computing applications. +- **State Management:** Effortlessly save and load your QuAM state, enabling consistent results and reproducibility in experiments. + +```python +from quam.components import * + +# Create a root-level QuAM instance +machine = BasicQuAM() + +# Add an OPX output channel +channel = SingleChannel(opx_output=("con1", 1)) +machine.channels["output"] = channel + +# Add a Gaussian pulse to the channel +channel.operations["gaussian"] = pulses.Gaussian( + length=100, amplitude=0.5, sigma=20 +) + +# Play the Gaussian pulse within a QUA program +with program() as prog: + channel.play("gaussian") + +# Generate the QUA configuration from QuAM +qua_configuration = machine.generate_config() + +# Save QuAM to a JSON file +machine.save("state.json") +``` +
+ + +## Getting Started + +- **[QuAM Installation](installation.md):** Set up QuAM on your system and get ready to explore its capabilities. +- **[QuAM Demonstration](demonstration.md):** Witness QuAM in action with practical examples and hands-on tutorials. +- **[QuAM Components](components/index.md):** Explore the core components that form the building blocks of the QuAM architecture. +- **[QuAM Features](features/index.md):** Discover the unique features and capabilities that QuAM offers for your quantum projects. +- **[QuAM Migration](migrating-to-quam.md)**: Already using QUA? Our detailed guide on migrating to QuAM are designed for a smooth transition to QuAM, letting you migrate your existing QUA projects without hassle. +- **[API References](API_references/index.md)**: Dive into the detailed API documentation to explore the classes, methods, and attributes available in QuAM. + +We are thrilled to support your journey into the quantum future with QuAM. Together, let's push the boundaries of what's possible in quantum computing! diff --git a/docs/getting-started.md b/docs/installation.md similarity index 96% rename from docs/getting-started.md rename to docs/installation.md index b2e1326f..ce0e4e02 100644 --- a/docs/getting-started.md +++ b/docs/installation.md @@ -1,4 +1,4 @@ -# Getting started +# QuAM Installation ## :one: Pre-requisites @@ -81,11 +81,11 @@ pip install ./quam If this raises a similar error, it likely means that Python cannot be found. Please check that you have Python installed. If you've set up a virtual environment, please ensure that it has been activated (see `Pre-requisites`). /// -## :three: Next steps +## :three: Next Steps QuAM comes with a range of standard QuAM components that can kick-start experimental setups. An example of these components are used can be found at the [QuAM demonstration](demonstration.md). -Beyond the standard components, QuAM provides a framework to create custom components that are tailored to a specific quantum setup. This allows you to create their own abstraction layers, enabling you to digitally represent and interact with your quantum setup. See [Creating custom QuAM components](custom-components.md) for details +Beyond the standard components, QuAM provides a framework to create custom components that are tailored to a specific quantum setup. This allows you to create their own abstraction layers, enabling you to digitally represent and interact with your quantum setup. See [Creating custom QuAM components](/components/custom-components) for details