tqec icon indicating copy to clipboard operation
tqec copied to clipboard

Y-Basis Initialization and Measurement

Open KabirDubey opened this issue 8 months ago • 24 comments

We want to support circuits with Y basis initialization and measurement. These are represented by the green rectangular prism in SketchUp. Fig 7 of [1] goes through the layer by layer construction, which involves sqrt{X} gates and some scheduling details.

In my opinion, the first step is a design document that outlines how this can be implemented in the layers framework and other modules. We can make sub-issues once the expected outcomes are better understood.

Anyone that is interested can be assigned to this issue.

[1] Inplace Access to the Surface Code Y Basis [2] Slides explaining the usage of Y Init and Meas

KabirDubey avatar Mar 27 '25 01:03 KabirDubey

A Y basis memory experiment is a good goal. Here's how it compares to a Z and X basis memory experiments:

Image

The shortest Y-basis memory experiment is shown on right (note the top face is green, not blue):

Image

You could run in principle run a memory experiement for longer, buttqec natively supports d rounds. Note the vertical connector has zero space-time volume, so all steps of the memory experiment are encoded in the Y prisms.

KabirDubey avatar Mar 27 '25 05:03 KabirDubey

The X, Y and Z memory experiments should be exactly the same length. In the SketchUp figure, they are all d rounds long.

On Wed, Mar 26, 2025, 10:52 PM Kabir Dubey @.***> wrote:

A Y basis memory experiment is a good goal. Here's how it compares to a Z and X basis memory experiments: my.screenshots.2025-03-27.at.12.18.00.AM.png (view on web) https://github.com/user-attachments/assets/a8c5aeb7-5ba5-4394-b1ce-ee14b259d27d

The shortest Y-basis memory experiment is shown on right (note the top face is green, not blue):

image.1.png (view on web) https://github.com/user-attachments/assets/3ec05557-e235-4280-aebc-525735a2c4d7

You could run in principle run a memory experiement for longer, buttqec natively supports d rounds of "hold". Note the vertical connector has zero space-time volume, so all steps of the memory experiment are encoded in the Y prisms.

— Reply to this email directly, view it on GitHub https://github.com/tqec/tqec/issues/548#issuecomment-2756783537, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAKAXTBEW65O45K7G6USIH32WOGZDAVCNFSM6AAAAABZ3WJLRCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDONJWG44DGNJTG4 . You are receiving this because you are subscribed to this thread.Message ID: @.***> [image: KabirDubey]KabirDubey left a comment (tqec/tqec#548) https://github.com/tqec/tqec/issues/548#issuecomment-2756783537

A Y basis memory experiment is a good goal. Here's how it compares to a Z and X basis memory experiments: my.screenshots.2025-03-27.at.12.18.00.AM.png (view on web) https://github.com/user-attachments/assets/a8c5aeb7-5ba5-4394-b1ce-ee14b259d27d

The shortest Y-basis memory experiment is shown on right (note the top face is green, not blue):

image.1.png (view on web) https://github.com/user-attachments/assets/3ec05557-e235-4280-aebc-525735a2c4d7

You could run in principle run a memory experiement for longer, buttqec natively supports d rounds of "hold". Note the vertical connector has zero space-time volume, so all steps of the memory experiment are encoded in the Y prisms.

— Reply to this email directly, view it on GitHub https://github.com/tqec/tqec/issues/548#issuecomment-2756783537, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAKAXTBEW65O45K7G6USIH32WOGZDAVCNFSM6AAAAABZ3WJLRCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDONJWG44DGNJTG4 . You are receiving this because you are subscribed to this thread.Message ID: @.***>

afowler avatar Mar 27 '25 05:03 afowler

Because I think it is of interest for this issue, below is a copy-paste of a message I sent on the Google group.


Hi,

The best way (I think) to implement Y-basis reset/measurement in TQEC will be to represent it with the layer approach (that should soon work on the main branch). It seems like the concept of "Chunk"s used in Craig's code is close (identical?) to our concept of "Layer": it represents one round of error correction. So the work of implementing Y-basis reset/measurement in TQEC consists in:

  1. implementing the needed layers (probably instances of tqec.compile.blocks.layer.atomic.raw.RawCircuitLayer),
  2. integrate these layers to for a (half-)Block and,
  3. update the soon-to-be-merged BaseBlockBuilder to make it aware of the new block and use it when asked for.

TQEC needs a way to have access to a function that will generate, from the scaling parameter k, a quantum circuit representing each layer (~QEC round).

Seems like the above code is not trivially usable for that. It might even be simpler/better to re-implement our own version and use Craig's code to validate it. That would avoid us to depend on external code that is not (to my knowledge) published on PyPI and that we would need to include directly in the tqec repository.

nelimee avatar Mar 27 '25 17:03 nelimee

How does rotate (block_graph.py) affect —or shouldn't affect— to a blockgraph previously built in SketchUp that contains a YHalfCube?

angelaelisa avatar Jun 09 '25 10:06 angelaelisa

How does rotate (block_graph.py) affect —or shouldn't affect— to a blockgraph previously built in SketchUp that contains a YHalfCube?

In theory, just like any BlockGraph. In practice, the YHalfCube currently only has 4 valid orientations (the half dimension should always be in the Z axis). Because it makes sense in theory, I am in favour of rotating the YHalfCube just like any other cube, and raise an error when generating the circuit (such as NotImplementedError)

nelimee avatar Jun 09 '25 11:06 nelimee

That's what I thought. I agree that raising an error is the easier way to work in practice. And at the front end, we could add an option to allow the user to delete the wrong positioned axis YHalfCube and add it in a new position.

angelaelisa avatar Jun 09 '25 11:06 angelaelisa

That's what I thought. I agree that raising an error is the easier way to work in practice. And at the front end, we could add an option to allow the user to delete the wrong positioned axis YHalfCube and add it in a new position.

At the BlockGraph level, any orientation of the YHalfCube make sense, so there is no need to treat the cube differently. It's just that we only have Craig's efficient implementation at the moment, and that implementation is only valid for specific orientations of the YHalfCube. There is no "wrong positioned axis YHalfCube", there is only "missing implementation of that cube" from the internals of tqec.

We could add an option to replace YHalfCube in non-implemented orientation with a structure that works. It should be doable for any situation, except if we have 2 YHalfCube on 1 cube (which I think is not possible because of YHalfCube taking half+constant layers instead of just half, but I am less sure about that).

nelimee avatar Jun 09 '25 12:06 nelimee

Ok, for now, just allow to change YHalfCube --> ZXCube when the YHalfCube generates a NotImplementedError, isn't it?

angelaelisa avatar Jun 09 '25 12:06 angelaelisa

Ok, for now, just allow to change YHalfCube --> ZXCube when the YHalfCube generates a NotImplementedError, isn't it?

What I can see in the frontend is an optional button that will try to change the computation such that there are no YHalfCube with the halved dimension on the X or Y axes. We could also try to implement that directly in the BlockGraph data-structure.

But I want to stress that a BlockGraph with a YHalfCube that has the halved dimension in the X or Y axes is completely valid. It is just that we do not know how to implement that as a quantum circuit at the moment.

nelimee avatar Jun 09 '25 14:06 nelimee

Quick question--how can I construct a Y HalfCube graph that passes block_graph.validate() despite not having exactly one pipe connected?

The following should trigger a NotImplementedError on main:

from tqec.computation.block_graph import BlockGraph
from tqec.computation.cube import YHalfCube
from tqec.utils.position import Position3D
from tqec import compile_block_graph

graph = BlockGraph()
y_cube_node = graph.add_cube(Position3D(0, 0, 0), YHalfCube())

print("Created Y memory block graph:")
print(f"Number of cubes: {len(graph.cubes)}")
print(f"Cube kind: {graph.cubes[0].kind}")
print(f"Cube type: {type(graph.cubes[0].kind).__name__}")

# Alias 
invalid_graph = graph

# Visualize the block graph
invalid_graph.view_as_html()

compiled_graph = compile_block_graph(invalid_graph)

but before it can do that, it fails this block_graph validation step because len(invalid_graph.pipes) == 0:

  def _validate_locally_at_cube(self, cube: Cube) -> None:
      """Check the validity of the block structures locally at a cube."""
      pipes = self.pipes_at(cube.position)
      # no fanout at ports
      if cube.is_port:
          if len(pipes) != 1:
              raise TQECError(
                  f"Port at {cube.position} does not have exactly one pipe connected."
              )
          return
      # time-like Y
      if cube.is_y_cube:
          if len(pipes) != 1:
              raise TQECError(
                  f"Y Half Cube at {cube.position} does not have exactly one pipe connected."
              )
          if not pipes[0].direction == Direction3D.Z:
              raise TQECError(f"Y Half Cube at {cube.position} has non-timelike pipes connected.")
          return

Is it possible to simulate just this the YHalfCube without adding an auxiliary pipe?

Based on the Basis.Z and Basis.X memory object memory demo, I think the control flow above should be changed. These objects are not ports by themselves (that is, their is_port attribute is false), so the same should be true for Y Cubes. Seems like the condition should be if cube.is_y_cube and cube._is_port? @nelimee @inmzhang

KabirDubey avatar Jul 20 '25 10:07 KabirDubey

I don't think a single YHalfCube makes any sense. A valid Y-basis memory experiment should be YHalfCube(Y-basis initialization) -> Temporal Pipe -> YHalfCube(Y-basis Measurement). And a Y cube is not a port. A port is a placeholder for further connection to other computation.

inmzhang avatar Jul 20 '25 11:07 inmzhang

I don't think a single YHalfCube makes any sense. A valid Y-basis memory experiment should be YHalfCube(Y-basis initialization) -> Temporal Pipe -> YHalfCube(Y-basis Measurement).

Ohh right, sorry I forgot about this basic definition, thanks!

KabirDubey avatar Jul 20 '25 18:07 KabirDubey

I wanted to confirm that the spec for a single Y HalfCube needs to somehow distinguish between initialization and measurement in order to compile the correct circuit. Essentially, ignoring repeated layers, measurement is in the order (switch, padding) whereas initialization is in the order (padding.invert(), switch.invert()).

For example, consider the memory experiment:

Image

The desired ScheduledCircuit is: (switch, padding, padding.invert(), switch.invert()), but if we do not distinguish between init and meas, I don't know how to avoid getting: (switch, padding, switch, padding) or (padding.invert(), switch.invert(),padding.invert(), switch.invert()).

If you agree making this distinction is necessary, what is the best way to set it programmatically (i.e. without having to define two separate blocks)? I was thinking of getting time ordering data from the layer tree of the computation, but I'm not sure.

KabirDubey avatar Jul 21 '25 05:07 KabirDubey

I wanted to confirm that the spec for a single Y HalfCube needs to somehow distinguish between initialization and measurement in order to compile the correct circuit.

Yes, and you should already have the information in the BlockGraph: if the YHalfCube has a pipe in the +Z direction, it is an initialisation, else if it has a pipe in the -Z direction it is a measurement (else, in any other case, that is a logical error).

what is the best way to set it programmatically (i.e. without having to define two separate blocks)?

I would say that the method implement the YHalfCube should take a boolean as input (maybe is_initialisation?) and generate the appropriate layers.

It might be possible to implement something along the lines of Block.invert, but that would require the ability to "invert" layers, and I am not sure that is doable without loosing at least some flexibility, which we want to avoid if possible. Because the YHalfCube only requires 2 different implementations, and these are fixed (and not generated/changing at runtime), I would say that it is perfectly fine implementing both by hand.

nelimee avatar Jul 21 '25 06:07 nelimee

Yes, and you should already have the information in the BlockGraph: if the YHalfCube has a pipe in the +Z direction, it is an initialisation, else if it has a pipe in the -Z direction it is a measurement (else, in any other case, that is a logical error).

With that, if the BlockGraph is rotated 180º, it will also run properly. Should we create an exception in rotate if the BlockGraph is rotated 90º or 270º (num_90_degree_rotation is an odd number) when containing a YHalfCube?

angelaelisa avatar Jul 21 '25 07:07 angelaelisa

It is checked during the block graph validation:

  def _validate_locally_at_cube(self, cube: Cube) -> None:
      """Check the validity of the block structures locally at a cube."""
      pipes = self.pipes_at(cube.position)
      # no fanout at ports
      if cube.is_port:
          if len(pipes) != 1:
              raise TQECError(
                  f"Port at {cube.position} does not have exactly one pipe connected."
              )
          return
      # time-like Y
      if cube.is_y_cube:
          if len(pipes) != 1:
              raise TQECError(
                  f"Y Half Cube at {cube.position} does not have exactly one pipe connected."
              )
          if not pipes[0].direction == Direction3D.Z:   # <------------------ HERE
              raise TQECError(f"Y Half Cube at {cube.position} has non-timelike pipes connected.")
          return

Maybe it's better to move this check into circuit compiling process because it's an exception in the sense that currently we do not have a valid circuit implementation of the Y cubes with 90/270 degree rotations (terminating the Y-basis correlation surface in the space direction).

inmzhang avatar Jul 21 '25 07:07 inmzhang

The gen directory in the midout source uses non-integer complex numbers e.g. 0.5+0.5j qubit coordinates. How would you advise mapping these to tqec's PhysicalQubitScalable2D, which only expects integers? Scaling and then adding an offset is an option, but perhaps there is a more robust approach that I am missing.

My design strategy is to write a function that maps chunks to raw circuits. This involves (1) converting Stim.Circuits to ScheduledCircuits; (2) extracting PhysicalQubitScalable2D from Chunk.q2i; (3) counting the number of moments in the Stim circuit; and (4) wrapping the chunk in a factory that appropriately handles the parameter k. AFAICT I'm having success with this approach with the exception of the qubit coordinates step (2) above.

I test the conversion using tqec blocks implemented in the fixed bulk convention. I generate tqec raw circuit objects from this chunk_to_raw_circuit function and then write pytests that automatically cross-check that the compiled output is the same Stim file as what tqec produces from collecting and compiling the layers.

Once I am able to generate the raw circuit layers for init and meas, I will put them as static files somewhere and then remove any dependencies I create to generate the layers.

KabirDubey avatar Jul 26 '25 02:07 KabirDubey

The gen directory in the midout source uses non-integer complex numbers e.g. 0.5+0.5j qubit coordinates. How would you advise mapping these to tqec's PhysicalQubitScalable2D, which only expects integers?

If the circuit ends up in a layer, you only need to scale it by 2 and create GridQubit instances from the resulting coordinates. The offset will be added by tqec when needed depending on the layer physical position.


One major issue I see with the above approach is scaling. How do you plan to scale for any user and runtime-provided value of k?

nelimee avatar Jul 26 '25 07:07 nelimee

@KabirDubey Sorry to butt in on the conversation focusing on what you are working on.

@nelimee If I understand correctly, k is the scaling factor of the template. How is this used to decide how many memory rounds r to add by the tool? Is there a relationship between the code distance d and syndrome measurement rounds? I think r = 2k - 1, and as the readme states k = 0.5(d-1). Is there a paper that discusses the relationship between d and r?

purva-thakre avatar Jul 26 '25 14:07 purva-thakre

@nelimee If I understand correctly, k is the scaling factor of the template. How is this used to decide how many memory rounds r to add by the tool? Is there a relationship between the code distance d and syndrome measurement rounds? I think r = 2k - 1, and as the readme states k = 0.5(d-1). Is there a paper that discusses the relationship between d and r?

Yes you understood correctly the factor k. For the moment, k is used as input to instances of LinearFunction to compute various things we need to compute. For example, LinearFunction(2, 1)(k) (i.e., 2*k + 1) is the expected distance. Both conventions use the same system to get the number of repetitions: there is a _DEFAULT_BLOCK_REPETITIONS value that is LinearFunction(2, -1) that encodes how many times the temporal bulk should be repeated, and this is r = 2*k - 1 as you wrote. #564 aims at making that controllable more easily.

Now, we basically need enough temporal repetitions to avoid a temporal error chain (i.e., an error at each round at the same place for d rounds) to break our code distance. This "enough" is the expected distance of the code (and that is why we represent "blocks" as cubes with the same distance in the 3 dimensions).

But the initial and final rounds in time are always special: either they are initialising/measuring the logical state, or they will be replaced by another layer from a connected pipe.

So we need d rounds, but the 2 extremal ones are special and are treated separately, so we need to repeat d-2 = 2*k - 1 times the "bulk rounds".

There are some recordings where we talked about that, but I will likely have a very hard time finding them back. Is the above enough?

nelimee avatar Jul 26 '25 14:07 nelimee

Complementary to the above:

First, it is important to note that we are discussing circuit distance as opposed to code distance—the latter depends solely on the construction of the QEC code. To preserve circuit distance, we require $d$ rounds, or $O(d)$ rounds, because we are performing lattice surgery (i.e., the merge-then-split operation). Lattice surgery introduces additional time boundaries that must be separated sufficiently to prevent time-like error chains from corrupting logical information, or to ensure the logical parity information can be reliably inferred from syndrome measurements. In contrast, if we use transversal logical operations instead of lattice surgery, the expected $O(d)$ rounds of syndrome measurements can be reduced to $O(1)$.

We use $d$ rounds for a simple memory experiment in tqec because it's simple to keep that consistent across the library. It does not mean that a memory experiment requires at least d rounds. Actually, a memory experiment (X/Z basis) can be as simple as 1 round. The separation between two opposite boundaries matters only when we want to protect correlation surfaces parallel to those boundaries.

I recommend reading the following resources:

  • Stability Experiment: https://arxiv.org/pdf/2204.13834
  • $O(1)$ rounds for transversal gates: https://arxiv.org/pdf/2403.03272

inmzhang avatar Jul 26 '25 15:07 inmzhang

I’ve been experimenting with implementing Y-basis initialization and measurement blocks, heavily relying on the gen package by Craig. While I have a nearly functional version, some issues still need to be resolved.

A YHalfCube consists of 1 + k + 1 rounds (O(d/2) + 1), which cannot perfectly align with the 1 + (2k - 1) + 1 rounds of other ZXCubes. To address this, we must align the circuits at the head or tail depending on whether it is an initialization or measurement block. If we adopt the layered approach, merging the layers becomes challenging when only the symbolic k expression is available. For instance, the first layer of Y-initialization should merge with the first layer of a parallel ZXCube for k=1, while for k=2, it needs to merge with a repeated memory layer.

To address these difficulties, I defined a new block type called InjectedBlock, which is handled separately from the LayeredBlock. The InjectedBlock is characterized by its circuit factory and an interface that specifies the in/out flows needed to correctly generate detectors. After compiling the layers of the circuit, we use the InjectedBlocks attached to the layer tree to "inject" the Y blocks into the circuit. This injection process operates on the generated stim.Circuit. During injection, all circuits at the same z step are flattened (i.e., unlooped) for proper merging and alignment.

Regarding the details of the Y-basis initialization/measurement circuit, the process transitions from a standard surface code patch with XZXZ alternating boundaries to a degenerate patch with XXZZ boundaries. Craig provided a circuit that transitions from a standard surface code using a fixed-bulk convention, with the top/bottom boundaries in the X basis. However, we need to adapt this circuit to support different conventions and boundary orientations. Initially, I thought we could simply rotate and reflect the circuit to generate all variants of convention × boundary orientation combinations. However, then I realized that the interaction (CX) order is correlated between the circuit leading up to the Y-basis measurement and the Y-basis measurement circuit itself (see this Stack Exchange post). To prevent a decrease in circuit distance, we need to adjust the CX interaction order in the Y-basis measurement circuit to match the convention and the type of connection used with the YHalfCube. This adjustment must be made without altering the implementation details of other blocks in TQEC. I have yet to develop a systematic approach for making these adjustments and will need to explore and experiment further.

Additionally, the observable in the original circuit aligns with the boundaries, but this alignment can be shifted to the midlines by applying a product of stabilizers. This alteration will slightly modify the measurements included in the observable.

The current code makes extensive use of the gen library for circuit construction, merging, and extra detector generation based on flow matching. Many concepts used by gen and TQEC are similar and in my mind the explicit flow definitions for the circuit chunks is really good for detectors/observables automation and circuit testing. Maybe we can integrate with it more in the future.

Before submitting a draft PR, I will need some time to figure out the circuit problem above and clean up the code as well as the git history.

inmzhang avatar Aug 30 '25 03:08 inmzhang

Quick update: I've worked out how to construct the correct circuit for different convention and boundary orientation by hand, but still need some time to clean up and write code. I expect to submit a draft PR this coming week.

inmzhang avatar Sep 28 '25 15:09 inmzhang

Hope we can have you present your work at one of the group meetings soon. We could shift the time to accommodate your time zone. Best, Austin.

On Sun, Sep 28, 2025 at 8:44 AM Yiming Zhang @.***> wrote:

inmzhang left a comment (tqec/tqec#548) https://github.com/tqec/tqec/issues/548#issuecomment-3343790978

Quick update: I've worked out how to construct the correct circuit for different convention and boundary orientation by hand, but still need some time to clean up and write code. I expect to submit a draft PR this coming week.

— Reply to this email directly, view it on GitHub https://github.com/tqec/tqec/issues/548#issuecomment-3343790978, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAKAXTBJDKVYBSNA2U4DLYT3U767RAVCNFSM6AAAAABZ3WJLRCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZTGNBTG44TAOJXHA . You are receiving this because you commented.Message ID: @.***>

afowler avatar Sep 28 '25 17:09 afowler