QEC architecture developer guide

This guide describes how QEC developers can build their own architectures using the Guppy FT framework. A QEC architecture is more than a library of logical gadgets: it includes the policies to manage resources at runtime (e.g. state factories) and the interfaces and compilation passes that enable others to use them with ease. On this page, we refer to the computational, logical and physical abstraction layers defined in the Getting Started page.

QEC architecture development in Guppy FT can be broken down into three milestones, listed in the table below. These milestone successively support more features and, crucially, enable a wider audience to use the architecture.

Milestone

Target use case

Development task

Implementation of QEC primitives

QEC prototyping and benchmarking

Implement the QEC gadgets in Guppy

Logical API

Application and QEC co-design

Formalize the architecture’s gate set

End-to-end encoding

“Push-button” encoding of applications

Specify how to compile arbitrary programs, and how to manage resources at runtime

QEC developers are encouraged to target the milestone that best fits the purpose and maturity of their architecture. This page provides a high-level summary of the steps to complete each milestone. We illustrate these with an example of a complete architecture for the Steane \([[7,1,3]]\) code. Note that more complex QEC architectures may require further innovation from developers. In such cases, we will be keen to hear your feedback, as we continue to evolve the framework to support a wider variety of architectures.

The guidelines laid out on this page are meant to promote the adoption of a common framework. Adhering to these guidelines will facilitate communication and adoption of new features.

Implementation of QEC primitives

We define a primitive as a Guppy function that implements a fundamental building block of the QEC architecture. As a rule of thumb, these are physical circuits such as state preparation, syndrome extraction, or transversal gates. We define these in a primitives module. The physical circuits may be written directly using Guppy’s guppylang.std.quantum library, or you may use guppylang.std.qsystem to make explicit use of the Quantinuum device gate set.

Guidelines

Primitives are the most fundamental building blocks of the architecture. For instance, magic-state preparation and injection are two separate primitives, but an injection-based \(T\) gate is not a primitive, as it comprises a preparation and an injection. The aim is to give expert users maximum control over how to compose primitives together, and easily replace them with alternative implementations. Compositions of primitives into logical gadgets, such as a \(T\) gate, belong to the logical API as convenience functions.

For primitives that require measurements, be mindful of where read() is called. This statement causes the program to block until the outcome is available, and this can be an obstacle for parallelization. When writing primitives, ensure you defer calling read() on measurements as late as possible.

To see an example of how to defer measurements to maximize parallelism, see the fault-tolerant \(T\ket{+}\) state preparation in the Steane architecture primitives. We wrap the logical block in a Guppy PreBlock struct that contains an array of Measurement and a force_check() method which calls read() on all measurements. This enables parallelization of state preparation via StateFactory.

Logical API

Providing a logical API to the QEC architecture enables describing, creating and transforming HUGRs at the logical abstraction layer. This includes:

  • developing compilation and optimization passes that transform logical programs;

  • supporting global management of resources, such as state factories;

  • writing programs at the logical level, which can then take advantage of the above features.

The steps for defining a logical API are the following:

  1. Logical HUGR extensions - Define logical operations and types to build HUGRs. This enables creating logical HUGRs, as well as implementing compilation and optimisation passes that transform them.

    • Generate two HUGR extensions using Rust: one for the operations and one for the types required by the architecture. The extensions are separated so that they can be versioned separately. Follow the Steane examples for ops.rs and types.rs.

    • Run just gen-extensions to automatically generate the JSON files of the HUGR extension.

  2. Logical Guppy bindings - Provide the Guppy interface to create programs directly at the logical level.

    • Follow the pattern in guppyft.code.steane.logical. The logical operations of the code include bindings for the operations and types in the HUGR extensions.

    • Additionally, developers may define composite logical operations. These composite operations should be written in terms of other logical operations, and should not include any guppylang.std.quantum operations An example of a composite operation in the Steane architecture is guppyft.code.steane.logical.t(), which is performed through magic-state injection.

  3. Define resource structures - Provide structures to manage logical resources at runtime, such as resource state generation. These resources will be architecture-dependent and are optional. For example, the Steane architecture uses state factories to parallelize preparation of \(\ket{0}\) and \(T\ket{+}\) states. A PreBlock Guppy struct wraps the logical block together with the flag measurements. The StateFactory then manages parallel preparation of PreBlocks. Similar resources could be defined to manage logical measurements or QEC cycles.

  4. Transformation passes - Define transformation passes between the computational, logical and physical abstraction layers. Depending on the chosen target milestone, these may be optional.

    • Define a compile pass that transforms the computational HUGR into a logical HUGR using the operations defined in step 1. In the Steane architecture, we use the guppyft.encode.ReplacementCompiler to replace supported guppylang.std.quantum operations with a corresponding operation from the guppyft.code.steane.logical API. More complex architectures will require a new HUGR compiler to be developed.

    • Define the implement_ops pass to link opaque logical API operations to their physical implementation. The Steane architecture uses the default implement_ops pass. See the section on end-to-end encoding for more detail.

    • Optionally, define QEC-aware optimization passes on the logical HUGR. For example, this could include optimizing qubit allocation into blocks for \(k>1\) codes.

Logical API versus primitives

Adding a logical API step is necessary to provide an abstraction layer on top of primitives, where we can define compilation and optimization passes and global management of resources. It provides a useful separation of concerns: we may build and transform logical HUGRs without involving the low-level physical details, then link their physical implementation as a final implement_ops pass. This abstraction lets developers provide a public interface for the logical operations, while keeping the details of the physical circuits private.

Guidelines

As a rule of thumb, the operations exposed in the HUGR extension should match the Guppy functions in primitives with the same name. Functions that are not to be exposed should be marked as private in primitives by beginning their name with an underscore.

You may provide composite functions in logical, such as the \(T\) gate implemented in guppyft.code.steane.logical.t(). These composite functions should not have corresponding HUGR operations; the HUGR extension should only include the fundamental primitives of the architecture.

Ideally, any logical compiler would support all quantum operations provided by guppylang.std.quantum. Naturally, you may choose to support only a subset of these. The encoding pass will fail on user programs outside this subset. We intend to provide default decompositions for some of these gates in future releases.

We recommend using the semver convention for the versioning of the HUGR extension.

End-to-end encoding

We are now able to combine the primitives and logical API building blocks into a single architecture builder to enable users to automatically encode their Guppy programs. This is illustrated in the end-to-end encoding notebook Automatic Encoding for the Steane Code.

Following our Steane example, our architecture is defined in guppyft.code.steane.encode which defines the SteaneBuilder class for users to build a specific architecture instance based on provided parameters.

A key enabler for Guppy FT is the ability to track and manage logical resources and operations at runtime. This is achieved by defining a Guppy struct that tracks a global state, making it available to logical operations. In the Steane architecture, SteaneBuilder defines the STATE struct to track and manage logical resources at runtime, which includes:

  • Allocation and freeing of logical blocks.

  • Parallel zero and magic state preparation through state factories.

  • Runtime QEC cycle insertion.

STATE tracks available logical-qubit addresses in a stack. Allocating a qubit pops an address from the stack, while freeing it returns the address to the stack. Addresses are (block_id, qubit_id) tuples. As Steane is a \(k=1\) code, qubit_id is fixed; however, representing addresses as tuples allows the same approach to support codes with multiple logical qubits per block.

The architecture wrapper creates the initial STATE and calls with_global() to run the encoded program in a global-enabled context. Replacement operation implementations then use map_global() to access the state: the current state is passed as the first argument to the replacement implementation, which returns the updated state followed by any ordinary return values. Examples of primitives being run in the global context can be found in SteaneBuilder.

Guidelines

Since Guppy supports classical logic at runtime, you may provide adaptive decompositions of gates, for instance following the approach from “Single-qubit rotation algorithm with logarithmic Toffoli count and gate depth” to support Rz gates with angles determined at runtime. Architectures where each code block contains multiple logical qubits can support arbitrary gate addressing by tracking the qubit assignment at runtime (as in the global STATE from encode) and using if statements to resolve how to decompose the computational gates into logical operations. Be mindful that complex classical computation at runtime can hinder performance on the quantum device if it causes stalling.

Further resources

  • Guppy language documentation.

  • HUGR repository: defines the IR of the Quantinuum software stack.

  • TKET repository: implements compilation and optimisation passes that act on HUGRs.