diff --git a/glue/stimflow/doc/api.md b/glue/stimflow/doc/api.md
index 978271da..99ee9b53 100644
--- a/glue/stimflow/doc/api.md
+++ b/glue/stimflow/doc/api.md
@@ -10,6 +10,7 @@
- [`stimflow.Chunk.find_logical_error`](#stimflow.Chunk.find_logical_error)
- [`stimflow.Chunk.flattened`](#stimflow.Chunk.flattened)
- [`stimflow.Chunk.from_circuit_with_mpp_boundaries`](#stimflow.Chunk.from_circuit_with_mpp_boundaries)
+ - [`stimflow.Chunk.missing_flow_generators`](#stimflow.Chunk.missing_flow_generators)
- [`stimflow.Chunk.start_code`](#stimflow.Chunk.start_code)
- [`stimflow.Chunk.start_interface`](#stimflow.Chunk.start_interface)
- [`stimflow.Chunk.start_patch`](#stimflow.Chunk.start_patch)
@@ -490,6 +491,63 @@ def from_circuit_with_mpp_boundaries(
) -> Chunk:
```
+
+```python
+# stimflow.Chunk.missing_flow_generators
+
+# (in class stimflow.Chunk)
+def missing_flow_generators(
+ self,
+) -> list[Flow]:
+ """Finds linearly independent flow generators that could be added to the chunk.
+
+ This method is intended as a debugging method when you're struggling to identify
+ the flow you forgot to declare. Beware that, just because this method returns a
+ flow, it doesn't mean you should actually declare it. For example, gauges in a
+ subsystem code correspond to flows you likely don't want to declare. Further beware
+ that, just because this method doesn't return a flow, it doesn't mean you don't want
+ to declare it. For example, if you intended to declare the X->X and Y->Y and Z->Z
+ flows of a logical qubit, but forgot to declare the Y->Y, this method will not return
+ that flow (because it's the product of the other two).
+
+ Returns:
+ A list of flows that the chunk's circuit supports, and that are linearly independent
+ of each other and of the existing flows declared by the chunk.
+
+ Raises:
+ ValueError: The flows declared by the chunk aren't valid. Can't infer which ones
+ are missing if the existing ones aren't valid in the first place.
+
+ Examples:
+ >>> import stim
+ >>> import stimflow as sf
+ >>> chunk = sf.Chunk(
+ ... # Distance 2 rep code idle cycle.
+ ... circuit=stim.Circuit('''
+ ... QUBIT_COORDS(0, 0) 0
+ ... QUBIT_COORDS(1, 0) 1
+ ... QUBIT_COORDS(2, 0) 2
+ ... R 1
+ ... CX 0 1 2 1
+ ... M 1
+ ... '''),
+ ... flows=[
+ ... sf.Flow(
+ ... start=sf.PauliMap.from_zs([0, 2]),
+ ... measurement_indices=[0],
+ ... ),
+ ... ],
+ ... )
+
+ >>> for e in chunk.missing_flow_generators():
+ ... print(e)
+ 1 -> Z[1+0j]*rec[0]
+ 1 -> Z[0+0j]*Z[2+0j]*rec[0]
+ Z[2+0j] -> Z[2+0j]
+ X[0+0j]*X[2+0j] -> X[0+0j]*X[2+0j]
+ """
+```
+
```python
# stimflow.Chunk.start_code
@@ -721,6 +779,7 @@ def with_edits(
*,
circuit: stim.Circuit | None = None,
q2i: dict[complex, int] | None = None,
+ o2i: dict[Any, int] | None = None,
flows: Iterable[Flow] | None = None,
discarded_inputs: Iterable[PauliMap] | None = None,
discarded_outputs: Iterable[PauliMap] | None = None,
@@ -832,57 +891,46 @@ class ChunkBuilder:
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(4+0j), (5+0j)]),
measurement_indices=(0,),
- center=(4.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(4+0j), (5+0j)]),
measurement_indices=(0,),
- center=(4.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(3+0j), (4+0j)]),
measurement_indices=(1,),
- center=(3.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(3+0j), (4+0j)]),
measurement_indices=(1,),
- center=(3.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(2+0j), (3+0j)]),
measurement_indices=(2,),
- center=(2.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(2+0j), (3+0j)]),
measurement_indices=(2,),
- center=(2.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(1+0j), (2+0j)]),
measurement_indices=(3,),
- center=(1.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(1+0j), (2+0j)]),
measurement_indices=(3,),
- center=(1.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([0j, (1+0j)]),
measurement_indices=(4,),
- center=(0.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([0j, (1+0j)]),
measurement_indices=(4,),
- center=(0.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap({0j: 'Z'}, obs_name='LZ'),
end=stimflow.PauliMap({0j: 'Z'}, obs_name='LZ'),
- center=0j,
),
],
)
@@ -1331,57 +1379,46 @@ def append(
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(4+0j), (5+0j)]),
measurement_indices=(0,),
- center=(4.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(4+0j), (5+0j)]),
measurement_indices=(0,),
- center=(4.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(3+0j), (4+0j)]),
measurement_indices=(1,),
- center=(3.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(3+0j), (4+0j)]),
measurement_indices=(1,),
- center=(3.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(2+0j), (3+0j)]),
measurement_indices=(2,),
- center=(2.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(2+0j), (3+0j)]),
measurement_indices=(2,),
- center=(2.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(1+0j), (2+0j)]),
measurement_indices=(3,),
- center=(1.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(1+0j), (2+0j)]),
measurement_indices=(3,),
- center=(1.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([0j, (1+0j)]),
measurement_indices=(4,),
- center=(0.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([0j, (1+0j)]),
measurement_indices=(4,),
- center=(0.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap({0j: 'Z'}, obs_name='LZ'),
end=stimflow.PauliMap({0j: 'Z'}, obs_name='LZ'),
- center=0j,
),
],
)
@@ -1407,15 +1444,12 @@ def append(
flows=[
stimflow.Flow(
end=stimflow.PauliMap.from_xs([0j, (1+0j)]),
- center=(0.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_xs([(1+0j), (2+0j)]),
- center=(1.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([0j, (1+0j), (2+0j)], obs_name='LZ'),
- center=(1+0j),
),
],
)
@@ -2663,7 +2697,6 @@ def __init__(
stimflow.Flow(
start=stimflow.PauliMap({0j: 'X'}),
measurement_indices=(1,),
- center=0j,
)
"""
```
@@ -2713,7 +2746,6 @@ def __mul__(
start=stimflow.PauliMap({(1+0j): 'X', (2+0j): 'Y'}),
end=stimflow.PauliMap({(2+0j): 'Y', (3+0j): 'Z'}),
measurement_indices=(-10, -1, 2, 20),
- center=(2+0j),
)
"""
```
@@ -2763,7 +2795,6 @@ def fused_with_next_flow(
start=stimflow.PauliMap({(1+0j): 'X'}),
end=stimflow.PauliMap({(3+0j): 'Z'}),
measurement_indices=(2, 90, 99, 120),
- center=(2+0j),
)
"""
```
@@ -2939,12 +2970,46 @@ def __init__(
extra_coords: Iterable[float] = (),
tag: str | None = ',
):
- """
+ """Initializes a FlowMetadata instance.
Args:
extra_coords: Extra numbers to add to DETECTOR coordinate arguments. By default stimflow
gives each detector an X, Y, and T coordinate. These numbers go afterward.
tag: A tag to attach to DETECTOR or OBSERVABLE_INCLUDE instructions.
+
+ Examples:
+ >>> import stim
+ >>> import stimflow as sf
+
+ >>> def metadata_func(flow: sf.Flow) -> sf.FlowMetadata:
+ ... if 'postselect' in flow.flags:
+ ... return sf.FlowMetadata(extra_coords=[-1])
+ ... elif 'color=r' in flow.flags:
+ ... return sf.FlowMetadata(tag="red")
+ ... elif 'color=g' in flow.flags:
+ ... return sf.FlowMetadata(tag="green", extra_coords=[5, 6, 7])
+ ... elif 'color=b' in flow.flags:
+ ... return sf.FlowMetadata(tag="blue")
+ ... else:
+ ... raise NotImplementedError(f"Couldn't figure out {flow}")
+
+ >>> compiler = sf.ChunkCompiler(metadata_func=metadata_func)
+ >>> compiler.append(sf.Chunk(
+ ... circuit=stim.Circuit('''
+ ... QUBIT_COORDS(0) 0
+ ... R 0
+ ... '''),
+ ... flows=[sf.Flow(end=sf.PauliMap.from_zs([0]), flags={"color=g"})],
+ ... ))
+ >>> compiler.append_magic_end_chunk()
+ >>> compiler.finish_circuit()
+ stim.Circuit('''
+ QUBIT_COORDS(0, 0) 0
+ R 0
+ TICK
+ MPP Z0
+ DETECTOR[green](0, 0, 0, 5, 6, 7) rec[-1]
+ ''')
"""
```
diff --git a/glue/stimflow/src/stimflow/_chunk/_chunk.py b/glue/stimflow/src/stimflow/_chunk/_chunk.py
index 9060bc6b..2bb00d1c 100644
--- a/glue/stimflow/src/stimflow/_chunk/_chunk.py
+++ b/glue/stimflow/src/stimflow/_chunk/_chunk.py
@@ -112,7 +112,13 @@ def __init__(
"""
flows = tuple(flows)
if q2i is None:
- q2i = {x + 1j * y: i for i, (x, y) in circuit.get_final_qubit_coordinates().items()}
+ def coords_to_complex(coords: list[float]) -> complex:
+ if len(coords) == 1:
+ return complex(coords[0])
+ if len(coords) == 2:
+ return coords[0] + 1j*coords[1]
+ raise NotImplementedError(f"Don't know how to convert circuit qubit coordinates {coords!r} into a complex position.")
+ q2i = {coords_to_complex(coords): i for i, coords in circuit.get_final_qubit_coordinates().items()}
for flow in flows:
for pauli_string in flow.start, flow.end:
for q in pauli_string.keys():
@@ -478,6 +484,7 @@ def with_edits(
*,
circuit: stim.Circuit | None = None,
q2i: dict[complex, int] | None = None,
+ o2i: dict[Any, int] | None = None,
flows: Iterable[Flow] | None = None,
discarded_inputs: Iterable[PauliMap] | None = None,
discarded_outputs: Iterable[PauliMap] | None = None,
@@ -487,6 +494,7 @@ def with_edits(
return Chunk(
circuit=self.circuit if circuit is None else circuit,
q2i=self.q2i if q2i is None else q2i,
+ o2i=self.o2i if o2i is None else o2i,
flows=self.flows if flows is None else flows,
discarded_inputs=(
self.discarded_inputs if discarded_inputs is None else discarded_inputs
@@ -1040,6 +1048,126 @@ def end_patch(self) -> Patch:
]
)
+ def missing_flow_generators(self) -> list[Flow]:
+ """Finds linearly independent flow generators that could be added to the chunk.
+
+ This method is intended as a debugging method when you're struggling to identify
+ the flow you forgot to declare. Beware that, just because this method returns a
+ flow, it doesn't mean you should actually declare it. For example, gauges in a
+ subsystem code correspond to flows you likely don't want to declare. Further beware
+ that, just because this method doesn't return a flow, it doesn't mean you don't want
+ to declare it. For example, if you intended to declare the X->X and Y->Y and Z->Z
+ flows of a logical qubit, but forgot to declare the Y->Y, this method will not return
+ that flow (because it's the product of the other two).
+
+ Returns:
+ A list of flows that the chunk's circuit supports, and that are linearly independent
+ of each other and of the existing flows declared by the chunk.
+
+ Raises:
+ ValueError: The flows declared by the chunk aren't valid. Can't infer which ones
+ are missing if the existing ones aren't valid in the first place.
+
+ Examples:
+ >>> import stim
+ >>> import stimflow as sf
+ >>> chunk = sf.Chunk(
+ ... # Distance 2 rep code idle cycle.
+ ... circuit=stim.Circuit('''
+ ... QUBIT_COORDS(0, 0) 0
+ ... QUBIT_COORDS(1, 0) 1
+ ... QUBIT_COORDS(2, 0) 2
+ ... R 1
+ ... CX 0 1 2 1
+ ... M 1
+ ... '''),
+ ... flows=[
+ ... sf.Flow(
+ ... start=sf.PauliMap.from_zs([0, 2]),
+ ... measurement_indices=[0],
+ ... ),
+ ... ],
+ ... )
+
+ >>> for e in chunk.missing_flow_generators():
+ ... print(e)
+ 1 -> Z[1+0j]*rec[0]
+ 1 -> Z[0+0j]*Z[2+0j]*rec[0]
+ Z[2+0j] -> Z[2+0j]
+ X[0+0j]*X[2+0j] -> X[0+0j]*X[2+0j]
+ """
+ self.verify(allow_overlapping_flows=True)
+
+ table_flows: list[stim.Flow] = []
+ for flow in self.flows:
+ table_flows.append(flow.to_stim_flow(q2i=self.q2i, o2i=self.o2i))
+ num_existing = len(table_flows)
+ table_flows.extend(self.circuit.flow_generators())
+
+ table_inputs: list[stim.PauliString] = [f.input_copy() for f in table_flows]
+ table_outputs: list[stim.PauliString] = [f.output_copy() for f in table_flows]
+ table_measurements: list[set[int]] = [set(f.measurements_copy()) for f in table_flows]
+
+ used_pivot_rows = set()
+ pivot_funcs = [
+ (self.circuit.num_qubits, lambda row, idx: len(table_inputs[row]) > idx and 1 <= table_inputs[row][idx] <= 2),
+ (self.circuit.num_qubits, lambda row, idx: len(table_inputs[row]) > idx and 2 <= table_inputs[row][idx] <= 3),
+ (self.circuit.num_qubits, lambda row, idx: len(table_outputs[row]) > idx and 1 <= table_outputs[row][idx] <= 2),
+ (self.circuit.num_qubits, lambda row, idx: len(table_outputs[row]) > idx and 2 <= table_outputs[row][idx] <= 3),
+ (self.circuit.num_measurements, lambda row, idx: idx in table_measurements[row]),
+ ]
+
+ def elim_step(q: int, func: Callable):
+ for pivot in range(len(table_flows)):
+ if pivot not in used_pivot_rows and func(pivot, q):
+ break
+ else:
+ return
+ used_pivot_rows.add(pivot)
+ for row in range(len(table_flows)):
+ if pivot != row and func(row, q):
+ table_measurements[row] ^= table_measurements[pivot]
+ table_inputs[row] *= table_inputs[pivot]
+ table_outputs[row] *= table_outputs[pivot]
+ table_flows[row] *= table_flows[pivot]
+
+ for num, func in pivot_funcs:
+ for idx in range(num):
+ elim_step(idx, func)
+ i2q = {i: q for q, i in self.q2i.items()}
+ i2o = {i: o for o, i in self.o2i.items()}
+ def s2p(s: stim.PauliString) -> PauliMap:
+ return PauliMap({
+ "X": [i2q[i] for i in s.pauli_indices("X")],
+ "Y": [i2q[i] for i in s.pauli_indices("Y")],
+ "Z": [i2q[i] for i in s.pauli_indices("Z")],
+ })
+
+ outputs: list[Flow] = []
+ for row in range(num_existing, len(table_flows)):
+ flow = table_flows[row]
+ inp = s2p(flow.input_copy())
+ out = s2p(flow.output_copy())
+ ms = flow.measurements_copy()
+ obs = flow.included_observables_copy()
+ if len(obs) > 1:
+ raise NotImplementedError(f'len({flow.included_observables_copy()=}) > 1')
+ if obs:
+ name = i2o.get(obs[0])
+ if name is None:
+ raise ValueError(f"A missing flow used obs index {obs=} but none of the values in the given {o2i=} matched that obs index.")
+ inp = inp.with_obs_name(name)
+ out = out.with_obs_name(name)
+ if inp or out or ms or obs:
+ outputs.append(Flow(
+ start=inp,
+ end=out,
+ measurement_indices=ms,
+ ))
+
+ return outputs
+
+
def _accumulate_observable_indices_used_by_circuit(circuit: stim.Circuit, *, out: set[int]):
for inst in circuit:
diff --git a/glue/stimflow/src/stimflow/_chunk/_chunk_builder.py b/glue/stimflow/src/stimflow/_chunk/_chunk_builder.py
index b181bd2a..e4147c1e 100644
--- a/glue/stimflow/src/stimflow/_chunk/_chunk_builder.py
+++ b/glue/stimflow/src/stimflow/_chunk/_chunk_builder.py
@@ -75,57 +75,46 @@ class ChunkBuilder:
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(4+0j), (5+0j)]),
measurement_indices=(0,),
- center=(4.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(4+0j), (5+0j)]),
measurement_indices=(0,),
- center=(4.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(3+0j), (4+0j)]),
measurement_indices=(1,),
- center=(3.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(3+0j), (4+0j)]),
measurement_indices=(1,),
- center=(3.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(2+0j), (3+0j)]),
measurement_indices=(2,),
- center=(2.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(2+0j), (3+0j)]),
measurement_indices=(2,),
- center=(2.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(1+0j), (2+0j)]),
measurement_indices=(3,),
- center=(1.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(1+0j), (2+0j)]),
measurement_indices=(3,),
- center=(1.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([0j, (1+0j)]),
measurement_indices=(4,),
- center=(0.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([0j, (1+0j)]),
measurement_indices=(4,),
- center=(0.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap({0j: 'Z'}, obs_name='LZ'),
end=stimflow.PauliMap({0j: 'Z'}, obs_name='LZ'),
- center=0j,
),
],
)
@@ -867,57 +856,46 @@ def append(
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(4+0j), (5+0j)]),
measurement_indices=(0,),
- center=(4.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(4+0j), (5+0j)]),
measurement_indices=(0,),
- center=(4.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(3+0j), (4+0j)]),
measurement_indices=(1,),
- center=(3.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(3+0j), (4+0j)]),
measurement_indices=(1,),
- center=(3.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(2+0j), (3+0j)]),
measurement_indices=(2,),
- center=(2.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(2+0j), (3+0j)]),
measurement_indices=(2,),
- center=(2.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([(1+0j), (2+0j)]),
measurement_indices=(3,),
- center=(1.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([(1+0j), (2+0j)]),
measurement_indices=(3,),
- center=(1.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap.from_zs([0j, (1+0j)]),
measurement_indices=(4,),
- center=(0.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([0j, (1+0j)]),
measurement_indices=(4,),
- center=(0.5+0j),
),
stimflow.Flow(
start=stimflow.PauliMap({0j: 'Z'}, obs_name='LZ'),
end=stimflow.PauliMap({0j: 'Z'}, obs_name='LZ'),
- center=0j,
),
],
)
@@ -943,15 +921,12 @@ def append(
flows=[
stimflow.Flow(
end=stimflow.PauliMap.from_xs([0j, (1+0j)]),
- center=(0.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_xs([(1+0j), (2+0j)]),
- center=(1.5+0j),
),
stimflow.Flow(
end=stimflow.PauliMap.from_zs([0j, (1+0j), (2+0j)], obs_name='LZ'),
- center=(1+0j),
),
],
)
diff --git a/glue/stimflow/src/stimflow/_chunk/_chunk_test.py b/glue/stimflow/src/stimflow/_chunk/_chunk_test.py
index d9960424..15e22979 100644
--- a/glue/stimflow/src/stimflow/_chunk/_chunk_test.py
+++ b/glue/stimflow/src/stimflow/_chunk/_chunk_test.py
@@ -580,3 +580,63 @@ def test_verify_distance():
],
)
chunk.verify_distance_is_at_least(3)
+
+
+def test_missing_flow_generators():
+ assert stimflow.Chunk(
+ circuit=stim.Circuit("""
+ R 0 1
+ H 0
+ CX 0 1
+ """),
+ flows=[],
+ q2i={0 + 1j: 0, 1 + 1j: 1},
+ o2i={},
+ ).missing_flow_generators() == [
+ stimflow.Flow(end=stimflow.PauliMap.from_xs([1j, 1 + 1j])),
+ stimflow.Flow(end=stimflow.PauliMap.from_zs([1j, 1 + 1j])),
+ ]
+
+ assert stimflow.Chunk(
+ circuit=stim.Circuit("""
+ R 0 1
+ H 0
+ CX 0 1
+ """),
+ flows=[stimflow.Flow(end=stimflow.PauliMap.from_ys([1j, 1 + 1j]))],
+ q2i={0 + 1j: 0, 1 + 1j: 1},
+ o2i={},
+ ).missing_flow_generators() == [
+ stimflow.Flow(end=stimflow.PauliMap.from_zs([1j, 1 + 1j])),
+ ]
+
+ chunk = stimflow.Chunk(
+ circuit=stim.Circuit("""
+ R 1
+ CX 0 1 2 1
+ M 1
+ """),
+ flows=[stimflow.Flow(start=stimflow.PauliMap.from_zs([0 + 1j, 2 + 1j]), measurement_indices=[0])],
+ q2i={0 + 1j: 0, 1 + 1j: 1, 2 + 1j: 2},
+ o2i={},
+ )
+ new_flows = chunk.missing_flow_generators()
+ chunk.with_edits(flows=[*chunk.flows, *new_flows]).verify()
+ assert new_flows == [
+ stimflow.Flow(
+ end=stimflow.PauliMap({(1+1j): 'Z'}),
+ measurement_indices=(0,),
+ ),
+ stimflow.Flow(
+ end=stimflow.PauliMap.from_zs([1j, (2+1j)]),
+ measurement_indices=(0,),
+ ),
+ stimflow.Flow(
+ start=stimflow.PauliMap({(2+1j): 'Z'}),
+ end=stimflow.PauliMap({(2+1j): 'Z'}),
+ ),
+ stimflow.Flow(
+ start=stimflow.PauliMap.from_xs([1j, (2+1j)]),
+ end=stimflow.PauliMap.from_xs([1j, (2+1j)]),
+ ),
+ ]
diff --git a/glue/stimflow/src/stimflow/_chunk/_flow_metadata.py b/glue/stimflow/src/stimflow/_chunk/_flow_metadata.py
index a3747636..6a41b625 100644
--- a/glue/stimflow/src/stimflow/_chunk/_flow_metadata.py
+++ b/glue/stimflow/src/stimflow/_chunk/_flow_metadata.py
@@ -7,12 +7,46 @@ class FlowMetadata:
"""Metadata, based on a flow, to use during circuit generation."""
def __init__(self, *, extra_coords: Iterable[float] = (), tag: str | None = ""):
- """
+ """Initializes a FlowMetadata instance.
Args:
extra_coords: Extra numbers to add to DETECTOR coordinate arguments. By default stimflow
gives each detector an X, Y, and T coordinate. These numbers go afterward.
tag: A tag to attach to DETECTOR or OBSERVABLE_INCLUDE instructions.
+
+ Examples:
+ >>> import stim
+ >>> import stimflow as sf
+
+ >>> def metadata_func(flow: sf.Flow) -> sf.FlowMetadata:
+ ... if 'postselect' in flow.flags:
+ ... return sf.FlowMetadata(extra_coords=[-1])
+ ... elif 'color=r' in flow.flags:
+ ... return sf.FlowMetadata(tag="red")
+ ... elif 'color=g' in flow.flags:
+ ... return sf.FlowMetadata(tag="green", extra_coords=[5, 6, 7])
+ ... elif 'color=b' in flow.flags:
+ ... return sf.FlowMetadata(tag="blue")
+ ... else:
+ ... raise NotImplementedError(f"Couldn't figure out {flow}")
+
+ >>> compiler = sf.ChunkCompiler(metadata_func=metadata_func)
+ >>> compiler.append(sf.Chunk(
+ ... circuit=stim.Circuit('''
+ ... QUBIT_COORDS(0) 0
+ ... R 0
+ ... '''),
+ ... flows=[sf.Flow(end=sf.PauliMap.from_zs([0]), flags={"color=g"})],
+ ... ))
+ >>> compiler.append_magic_end_chunk()
+ >>> compiler.finish_circuit()
+ stim.Circuit('''
+ QUBIT_COORDS(0, 0) 0
+ R 0
+ TICK
+ MPP Z0
+ DETECTOR[green](0, 0, 0, 5, 6, 7) rec[-1]
+ ''')
"""
self.extra_coords: tuple[float, ...] = tuple(extra_coords)
self.tag: str = tag or ""
diff --git a/glue/stimflow/src/stimflow/_core/_flow.py b/glue/stimflow/src/stimflow/_core/_flow.py
index be6e2348..54114ca2 100644
--- a/glue/stimflow/src/stimflow/_core/_flow.py
+++ b/glue/stimflow/src/stimflow/_core/_flow.py
@@ -52,7 +52,6 @@ def __init__(
stimflow.Flow(
start=stimflow.PauliMap({0j: 'X'}),
measurement_indices=(1,),
- center=0j,
)
"""
if start is not None and not isinstance(start, (PauliMap, Tile)):
@@ -331,7 +330,11 @@ def __repr__(self):
lines.append(f" measurement_indices={self.measurement_indices!r},")
if self.flags:
lines.append(f" flags={self.flags!r},")
- if self.center is not None:
+ if self.start or self.end:
+ inferred_center = sum([*self.start.keys(), *self.end.keys()]) / (len(self.start) + len(self.end))
+ else:
+ inferred_center = None
+ if inferred_center != self.center:
lines.append(f" center={self.center!r},")
if self.sign is not None:
lines.append(f" sign={self.sign!r},")
@@ -383,7 +386,6 @@ def fused_with_next_flow(self, next_flow: Flow, *, next_flow_measure_offset: int
start=stimflow.PauliMap({(1+0j): 'X'}),
end=stimflow.PauliMap({(3+0j): 'Z'}),
measurement_indices=(2, 90, 99, 120),
- center=(2+0j),
)
"""
if next_flow.start != self.end:
@@ -449,7 +451,6 @@ def __mul__(self, other: Flow) -> Flow:
start=stimflow.PauliMap({(1+0j): 'X', (2+0j): 'Y'}),
end=stimflow.PauliMap({(2+0j): 'Y', (3+0j): 'Z'}),
measurement_indices=(-10, -1, 2, 20),
- center=(2+0j),
)
"""
if self.obs_name != other.obs_name: