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: