stimcirq 1.15.dev1733303566__tar.gz → 1.15.dev1733420295__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/PKG-INFO +1 -1
  2. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/setup.py +1 -1
  3. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/__init__.py +1 -1
  4. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_cirq_to_stim.py +80 -54
  5. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_cirq_to_stim_test.py +31 -13
  6. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_cx_swap_gate.py +2 -2
  7. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_cz_swap_gate.py +2 -2
  8. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_det_annotation.py +3 -1
  9. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_measure_and_or_reset_gate.py +3 -3
  10. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_obs_annotation.py +3 -1
  11. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_shift_coords_annotation.py +2 -2
  12. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_stim_to_cirq.py +58 -14
  13. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_stim_to_cirq_test.py +69 -0
  14. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq.egg-info/PKG-INFO +1 -1
  15. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/README.md +0 -0
  16. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/setup.cfg +0 -0
  17. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_cx_swap_test.py +0 -0
  18. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_cz_swap_test.py +0 -0
  19. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_det_annotation_test.py +0 -0
  20. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_measure_and_or_reset_gate_test.py +0 -0
  21. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_obs_annotation_test.py +0 -0
  22. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_shift_coords_annotation_test.py +0 -0
  23. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_stim_sampler.py +0 -0
  24. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_stim_sampler_test.py +0 -0
  25. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_sweep_pauli.py +0 -0
  26. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_sweep_pauli_test.py +0 -0
  27. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_two_qubit_asymmetric_depolarize.py +0 -0
  28. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq/_two_qubit_asymmetric_depolarize_test.py +0 -0
  29. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq.egg-info/SOURCES.txt +0 -0
  30. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq.egg-info/dependency_links.txt +0 -0
  31. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq.egg-info/requires.txt +0 -0
  32. {stimcirq-1.15.dev1733303566 → stimcirq-1.15.dev1733420295}/stimcirq.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stimcirq
3
- Version: 1.15.dev1733303566
3
+ Version: 1.15.dev1733420295
4
4
  Summary: Implements a cirq.Sampler backed by stim.
5
5
  Home-page: https://github.com/quantumlib/stim
6
6
  Author: Craig Gidney
@@ -17,7 +17,7 @@ from setuptools import setup
17
17
  with open('README.md', encoding='UTF-8') as f:
18
18
  long_description = f.read()
19
19
 
20
- __version__ = '1.15.dev1733303566'
20
+ __version__ = '1.15.dev1733420295'
21
21
 
22
22
  setup(
23
23
  name='stimcirq',
@@ -1,4 +1,4 @@
1
- __version__ = '1.15.dev1733303566'
1
+ __version__ = '1.15.dev1733420295'
2
2
  from ._cirq_to_stim import cirq_circuit_to_stim_circuit
3
3
  from ._cx_swap_gate import CXSwapGate
4
4
  from ._cz_swap_gate import CZSwapGate
@@ -7,8 +7,18 @@ import cirq
7
7
  import stim
8
8
 
9
9
 
10
+ def _forward_single_str_tag(op: cirq.CircuitOperation) -> str:
11
+ tags = [tag for tag in op.tags if isinstance(tag, str)]
12
+ if len(tags) == 1:
13
+ return tags[0]
14
+ return ""
15
+
16
+
10
17
  def cirq_circuit_to_stim_circuit(
11
- circuit: cirq.AbstractCircuit, *, qubit_to_index_dict: Optional[Dict[cirq.Qid, int]] = None
18
+ circuit: cirq.AbstractCircuit,
19
+ *,
20
+ qubit_to_index_dict: Optional[Dict[cirq.Qid, int]] = None,
21
+ tag_func: Callable[[cirq.Operation], str] = _forward_single_str_tag,
12
22
  ) -> stim.Circuit:
13
23
  """Converts a cirq circuit into an equivalent stim circuit.
14
24
 
@@ -33,6 +43,10 @@ def cirq_circuit_to_stim_circuit(
33
43
  circuit: The circuit to convert.
34
44
  qubit_to_index_dict: Optional. Which integer each qubit should get mapped to. If not specified, defaults to
35
45
  indexing qubits in the circuit in sorted order.
46
+ tag_func: Controls the tag attached to the stim instructions the cirq operation turns
47
+ into. If not specified, defaults to checking for string tags on the circuit operation
48
+ and if there is exactly one string tag then using that tag (otherwise not specifying a
49
+ tag).
36
50
 
37
51
  Returns:
38
52
  The converted circuit.
@@ -85,21 +99,29 @@ def cirq_circuit_to_stim_circuit(
85
99
  # The indices of qubits the gate is operating on.
86
100
  targets: List[int],
87
101
 
102
+ # A custom string associated with the operation, which can be tagged
103
+ # onto any operations appended to the stim circuit.
104
+ tag: str,
105
+
88
106
  # Forward compatibility with future arguments.
89
107
  **kwargs):
90
108
 
91
109
  edit_circuit.append_operation("H", targets)
92
110
  """
93
- return cirq_circuit_to_stim_data(circuit, q2i=qubit_to_index_dict, flatten=False)[0]
111
+ return cirq_circuit_to_stim_data(circuit, q2i=qubit_to_index_dict, flatten=False, tag_func=tag_func)[0]
94
112
 
95
113
 
96
114
  def cirq_circuit_to_stim_data(
97
- circuit: cirq.AbstractCircuit, *, q2i: Optional[Dict[cirq.Qid, int]] = None, flatten: bool = False,
115
+ circuit: cirq.AbstractCircuit,
116
+ *,
117
+ q2i: Optional[Dict[cirq.Qid, int]] = None,
118
+ flatten: bool = False,
119
+ tag_func: Callable[[cirq.Operation], str] = _forward_single_str_tag,
98
120
  ) -> Tuple[stim.Circuit, List[Tuple[str, int]]]:
99
121
  """Converts a Cirq circuit into a Stim circuit and also metadata about where measurements go."""
100
122
  if q2i is None:
101
123
  q2i = {q: i for i, q in enumerate(sorted(circuit.all_qubits()))}
102
- helper = CirqToStimHelper()
124
+ helper = CirqToStimHelper(tag_func=tag_func)
103
125
  helper.q2i = q2i
104
126
  helper.flatten = flatten
105
127
 
@@ -115,11 +137,11 @@ def cirq_circuit_to_stim_data(
115
137
  return helper.out, helper.key_out
116
138
 
117
139
 
118
- StimTypeHandler = Callable[[stim.Circuit, cirq.Gate, List[int]], None]
140
+ StimTypeHandler = Callable[[stim.Circuit, cirq.Gate, List[int], str], None]
119
141
 
120
142
 
121
143
  @functools.lru_cache(maxsize=1)
122
- def gate_to_stim_append_func() -> Dict[cirq.Gate, Callable[[stim.Circuit, List[int]], None]]:
144
+ def gate_to_stim_append_func() -> Dict[cirq.Gate, Callable[[stim.Circuit, List[int], str], None]]:
123
145
  """A dictionary mapping specific gate instances to stim circuit appending functions."""
124
146
  x = (cirq.X, False)
125
147
  y = (cirq.Y, False)
@@ -128,29 +150,29 @@ def gate_to_stim_append_func() -> Dict[cirq.Gate, Callable[[stim.Circuit, List[i
128
150
  ny = (cirq.Y, True)
129
151
  nz = (cirq.Z, True)
130
152
 
131
- def do_nothing(c, t):
153
+ def do_nothing(_gates, _targets, tag):
132
154
  pass
133
155
 
134
156
  def use(
135
157
  *gates: str, individuals: Sequence[Tuple[str, int]] = ()
136
- ) -> Callable[[stim.Circuit, List[int]], None]:
158
+ ) -> Callable[[stim.Circuit, List[int], str], None]:
137
159
  if len(gates) == 1 and not individuals:
138
160
  (g,) = gates
139
- return lambda c, t: c.append_operation(g, t)
161
+ return lambda c, t, tag: c.append(g, t, tag=tag)
140
162
 
141
163
  if not individuals:
142
164
 
143
- def do(c, t):
165
+ def do(c, t, tag: str):
144
166
  for g in gates:
145
- c.append_operation(g, t)
167
+ c.append(g, t, tag=tag)
146
168
 
147
169
  else:
148
170
 
149
- def do(c, t):
171
+ def do(c, t, tag: str):
150
172
  for g in gates:
151
- c.append_operation(g, t)
173
+ c.append(g, t, tag=tag)
152
174
  for g, k in individuals:
153
- c.append_operation(g, [t[k]])
175
+ c.append(g, [t[k]], tag=tag)
154
176
 
155
177
  return do
156
178
 
@@ -238,14 +260,14 @@ def gate_type_to_stim_append_func() -> Dict[Type[cirq.Gate], StimTypeHandler]:
238
260
  cirq.AsymmetricDepolarizingChannel: cast(
239
261
  StimTypeHandler, _stim_append_asymmetric_depolarizing_channel
240
262
  ),
241
- cirq.BitFlipChannel: lambda c, g, t: c.append_operation(
242
- "X_ERROR", t, cast(cirq.BitFlipChannel, g).p
263
+ cirq.BitFlipChannel: lambda c, g, t, tag: c.append(
264
+ "X_ERROR", t, cast(cirq.BitFlipChannel, g).p, tag=tag
243
265
  ),
244
- cirq.PhaseFlipChannel: lambda c, g, t: c.append_operation(
245
- "Z_ERROR", t, cast(cirq.PhaseFlipChannel, g).p
266
+ cirq.PhaseFlipChannel: lambda c, g, t, tag: c.append(
267
+ "Z_ERROR", t, cast(cirq.PhaseFlipChannel, g).p, tag=tag
246
268
  ),
247
- cirq.PhaseDampingChannel: lambda c, g, t: c.append_operation(
248
- "Z_ERROR", t, 0.5 - math.sqrt(1 - cast(cirq.PhaseDampingChannel, g).gamma) / 2
269
+ cirq.PhaseDampingChannel: lambda c, g, t, tag: c.append(
270
+ "Z_ERROR", t, 0.5 - math.sqrt(1 - cast(cirq.PhaseDampingChannel, g).gamma) / 2, tag=tag
249
271
  ),
250
272
  cirq.RandomGateChannel: cast(StimTypeHandler, _stim_append_random_gate_channel),
251
273
  cirq.DepolarizingChannel: cast(StimTypeHandler, _stim_append_depolarizing_channel),
@@ -253,16 +275,16 @@ def gate_type_to_stim_append_func() -> Dict[Type[cirq.Gate], StimTypeHandler]:
253
275
 
254
276
 
255
277
  def _stim_append_measurement_gate(
256
- circuit: stim.Circuit, gate: cirq.MeasurementGate, targets: List[int]
278
+ circuit: stim.Circuit, gate: cirq.MeasurementGate, targets: List[int], tag: str
257
279
  ):
258
280
  for i, b in enumerate(gate.invert_mask):
259
281
  if b:
260
282
  targets[i] = stim.target_inv(targets[i])
261
- circuit.append_operation("M", targets)
283
+ circuit.append("M", targets, tag=tag)
262
284
 
263
285
 
264
286
  def _stim_append_pauli_measurement_gate(
265
- circuit: stim.Circuit, gate: cirq.PauliMeasurementGate, targets: List[int]
287
+ circuit: stim.Circuit, gate: cirq.PauliMeasurementGate, targets: List[int], tag: str
266
288
  ):
267
289
  obs: cirq.DensePauliString = gate.observable()
268
290
 
@@ -287,11 +309,11 @@ def _stim_append_pauli_measurement_gate(
287
309
  if obs.coefficient != 1 and obs.coefficient != -1:
288
310
  raise NotImplementedError(f"obs.coefficient={obs.coefficient!r} not in [1, -1]")
289
311
 
290
- circuit.append_operation("MPP", new_targets)
312
+ circuit.append("MPP", new_targets, tag=tag)
291
313
 
292
314
 
293
315
  def _stim_append_spp_gate(
294
- circuit: stim.Circuit, gate: cirq.PauliStringPhasorGate, targets: List[int]
316
+ circuit: stim.Circuit, gate: cirq.PauliStringPhasorGate, targets: List[int], tag: str
295
317
  ):
296
318
  obs: cirq.DensePauliString = gate.dense_pauli_string
297
319
  a = gate.exponent_neg
@@ -312,26 +334,26 @@ def _stim_append_spp_gate(
312
334
  return False
313
335
  new_targets.pop()
314
336
 
315
- circuit.append_operation("SPP" if d == 0.5 else "SPP_DAG", new_targets)
337
+ circuit.append("SPP" if d == 0.5 else "SPP_DAG", new_targets, tag=tag)
316
338
  return True
317
339
 
318
340
 
319
341
  def _stim_append_dense_pauli_string_gate(
320
- c: stim.Circuit, g: cirq.BaseDensePauliString, t: List[int]
342
+ c: stim.Circuit, g: cirq.BaseDensePauliString, t: List[int], tag: str
321
343
  ):
322
344
  gates = [None, "X", "Y", "Z"]
323
345
  for p, k in zip(g.pauli_mask, t):
324
346
  if p:
325
- c.append_operation(gates[p], [k])
347
+ c.append(gates[p], [k], tag=tag)
326
348
 
327
349
 
328
350
  def _stim_append_asymmetric_depolarizing_channel(
329
- c: stim.Circuit, g: cirq.AsymmetricDepolarizingChannel, t: List[int]
351
+ c: stim.Circuit, g: cirq.AsymmetricDepolarizingChannel, t: List[int], tag: str
330
352
  ):
331
353
  if cirq.num_qubits(g) == 1:
332
- c.append_operation("PAULI_CHANNEL_1", t, [g.p_x, g.p_y, g.p_z])
354
+ c.append("PAULI_CHANNEL_1", t, [g.p_x, g.p_y, g.p_z], tag=tag)
333
355
  elif cirq.num_qubits(g) == 2:
334
- c.append_operation(
356
+ c.append(
335
357
  "PAULI_CHANNEL_2",
336
358
  t,
337
359
  [
@@ -350,33 +372,34 @@ def _stim_append_asymmetric_depolarizing_channel(
350
372
  g.error_probabilities.get('ZX', 0),
351
373
  g.error_probabilities.get('ZY', 0),
352
374
  g.error_probabilities.get('ZZ', 0),
353
- ]
375
+ ],
376
+ tag=tag,
354
377
  )
355
378
  else:
356
379
  raise NotImplementedError(f'cirq-to-stim gate {g!r}')
357
380
 
358
381
 
359
- def _stim_append_depolarizing_channel(c: stim.Circuit, g: cirq.DepolarizingChannel, t: List[int]):
382
+ def _stim_append_depolarizing_channel(c: stim.Circuit, g: cirq.DepolarizingChannel, t: List[int], tag: str):
360
383
  if g.num_qubits() == 1:
361
- c.append_operation("DEPOLARIZE1", t, g.p)
384
+ c.append("DEPOLARIZE1", t, g.p, tag=tag)
362
385
  elif g.num_qubits() == 2:
363
- c.append_operation("DEPOLARIZE2", t, g.p)
386
+ c.append("DEPOLARIZE2", t, g.p, tag=tag)
364
387
  else:
365
388
  raise TypeError(f"Don't know how to turn {g!r} into Stim operations.")
366
389
 
367
390
 
368
- def _stim_append_controlled_gate(c: stim.Circuit, g: cirq.ControlledGate, t: List[int]):
391
+ def _stim_append_controlled_gate(c: stim.Circuit, g: cirq.ControlledGate, t: List[int], tag: str):
369
392
  if isinstance(g.sub_gate, cirq.BaseDensePauliString) and g.num_controls() == 1:
370
393
  gates = [None, "CX", "CY", "CZ"]
371
394
  for p, k in zip(g.sub_gate.pauli_mask, t[1:]):
372
395
  if p:
373
- c.append_operation(gates[p], [t[0], k])
396
+ c.append(gates[p], [t[0], k], tag=tag)
374
397
  if g.sub_gate.coefficient == 1j:
375
- c.append_operation("S", t[:1])
398
+ c.append("S", t[:1], tag=tag)
376
399
  elif g.sub_gate.coefficient == -1:
377
- c.append_operation("Z", t[:1])
400
+ c.append("Z", t[:1], tag=tag)
378
401
  elif g.sub_gate.coefficient == -1j:
379
- c.append_operation("S_DAG", t[:1])
402
+ c.append("S_DAG", t[:1], tag=tag)
380
403
  elif g.sub_gate.coefficient == 1:
381
404
  pass
382
405
  else:
@@ -386,13 +409,13 @@ def _stim_append_controlled_gate(c: stim.Circuit, g: cirq.ControlledGate, t: Lis
386
409
  raise TypeError(f"Don't know how to turn controlled gate {g!r} into Stim operations.")
387
410
 
388
411
 
389
- def _stim_append_random_gate_channel(c: stim.Circuit, g: cirq.RandomGateChannel, t: List[int]):
412
+ def _stim_append_random_gate_channel(c: stim.Circuit, g: cirq.RandomGateChannel, t: List[int], tag: str):
390
413
  if g.sub_gate in [cirq.X, cirq.Y, cirq.Z]:
391
- c.append_operation(f"{g.sub_gate}_ERROR", t, g.probability)
414
+ c.append(f"{g.sub_gate}_ERROR", t, g.probability, tag=tag)
392
415
  elif isinstance(g.sub_gate, cirq.DensePauliString):
393
416
  target_p = [None, stim.target_x, stim.target_y, stim.target_z]
394
417
  pauli_targets = [target_p[p](t) for t, p in zip(t, g.sub_gate.pauli_mask) if p]
395
- c.append_operation(f"CORRELATED_ERROR", pauli_targets, g.probability)
418
+ c.append(f"CORRELATED_ERROR", pauli_targets, g.probability, tag=tag)
396
419
  else:
397
420
  raise NotImplementedError(
398
421
  f"Don't know how to turn probabilistic {g!r} into Stim operations."
@@ -400,33 +423,35 @@ def _stim_append_random_gate_channel(c: stim.Circuit, g: cirq.RandomGateChannel,
400
423
 
401
424
 
402
425
  class CirqToStimHelper:
403
- def __init__(self):
426
+ def __init__(self, tag_func: Callable[[cirq.Operation], str]):
404
427
  self.key_out: List[Tuple[str, int]] = []
405
428
  self.out = stim.Circuit()
406
429
  self.q2i = {}
407
430
  self.have_seen_loop = False
408
431
  self.flatten = False
432
+ self.tag_func = tag_func
409
433
 
410
- def process_circuit_operation_into_repeat_block(self, op: cirq.CircuitOperation) -> None:
434
+ def process_circuit_operation_into_repeat_block(self, op: cirq.CircuitOperation, tag: str) -> None:
411
435
  if self.flatten or op.repetitions == 1:
412
436
  moments = cirq.unroll_circuit_op(cirq.Circuit(op), deep=False, tags_to_check=None).moments
413
437
  self.process_moments(moments)
414
438
  self.out = self.out[:-1] # Remove a trailing TICK (to avoid double TICK)
415
439
  return
416
440
 
417
- child = CirqToStimHelper()
441
+ child = CirqToStimHelper(tag_func=self.tag_func)
418
442
  child.key_out = self.key_out
419
443
  child.q2i = self.q2i
420
444
  child.have_seen_loop = True
421
445
  self.have_seen_loop = True
422
446
  child.process_moments(op.transform_qubits(lambda q: op.qubit_map.get(q, q)).circuit)
423
- self.out += child.out * op.repetitions
447
+ self.out.append(stim.CircuitRepeatBlock(op.repetitions, child.out, tag=tag))
424
448
 
425
449
  def process_operations(self, operations: Iterable[cirq.Operation]) -> None:
426
450
  g2f = gate_to_stim_append_func()
427
451
  t2f = gate_type_to_stim_append_func()
428
452
  for op in operations:
429
453
  assert isinstance(op, cirq.Operation)
454
+ tag = self.tag_func(op)
430
455
  op = op.untagged
431
456
  gate = op.gate
432
457
  targets = [self.q2i[q] for q in op.qubits]
@@ -441,36 +466,37 @@ class CirqToStimHelper:
441
466
  edit_measurement_key_lengths=self.key_out,
442
467
  targets=targets,
443
468
  have_seen_loop=self.have_seen_loop,
469
+ tag=tag,
444
470
  )
445
471
  continue
446
472
 
447
473
  if isinstance(op, cirq.CircuitOperation):
448
- self.process_circuit_operation_into_repeat_block(op)
474
+ self.process_circuit_operation_into_repeat_block(op, tag=tag)
449
475
  continue
450
476
 
451
477
  # Special case measurement, because of its metadata.
452
478
  if isinstance(gate, cirq.PauliStringPhasorGate):
453
- if _stim_append_spp_gate(self.out, gate, targets):
479
+ if _stim_append_spp_gate(self.out, gate, targets, tag=tag):
454
480
  continue
455
481
  if isinstance(gate, cirq.PauliMeasurementGate):
456
482
  self.key_out.append((gate.key, len(targets)))
457
- _stim_append_pauli_measurement_gate(self.out, gate, targets)
483
+ _stim_append_pauli_measurement_gate(self.out, gate, targets, tag=tag)
458
484
  continue
459
485
  if isinstance(gate, cirq.MeasurementGate):
460
486
  self.key_out.append((gate.key, len(targets)))
461
- _stim_append_measurement_gate(self.out, gate, targets)
487
+ _stim_append_measurement_gate(self.out, gate, targets, tag=tag)
462
488
  continue
463
489
 
464
490
  # Look for recognized gate values like cirq.H.
465
491
  val_append_func = g2f.get(gate)
466
492
  if val_append_func is not None:
467
- val_append_func(self.out, targets)
493
+ val_append_func(self.out, targets, tag=tag)
468
494
  continue
469
495
 
470
496
  # Look for recognized gate types like cirq.DepolarizingChannel.
471
497
  type_append_func = t2f.get(type(gate))
472
498
  if type_append_func is not None:
473
- type_append_func(self.out, gate, targets)
499
+ type_append_func(self.out, gate, targets, tag=tag)
474
500
  continue
475
501
 
476
502
  # Ask unrecognized operations to decompose themselves into simpler operations.
@@ -489,7 +515,7 @@ class CirqToStimHelper:
489
515
 
490
516
  # Append a TICK, unless it was already handled by an internal REPEAT block.
491
517
  if length_before == len(self.out) or not isinstance(self.out[-1], stim.CircuitRepeatBlock):
492
- self.out.append_operation("TICK", [])
518
+ self.out.append("TICK", [])
493
519
 
494
520
  def process_moments(self, moments: Iterable[cirq.Moment]):
495
521
  for moment in moments:
@@ -74,21 +74,21 @@ def assert_unitary_gate_converts_correctly(gate: cirq.Gate):
74
74
  # If the gate is translated correctly, the measurement will always be zero.
75
75
 
76
76
  c = stim.Circuit()
77
- c.append_operation("H", range(n))
77
+ c.append("H", range(n))
78
78
  for i in range(n):
79
- c.append_operation("CNOT", [i, i + n])
80
- c.append_operation("H", [2 * n])
79
+ c.append("CNOT", [i, i + n])
80
+ c.append("H", [2 * n])
81
81
  for q, p in pre.items():
82
- c.append_operation(f"C{p}", [2 * n, q.x])
82
+ c.append(f"C{p}", [2 * n, q.x])
83
83
  qs = cirq.LineQubit.range(n)
84
84
  conv_gate, _ = cirq_circuit_to_stim_data(cirq.Circuit(gate(*qs)), q2i={q: q.x for q in qs})
85
85
  c += conv_gate
86
86
  for q, p in post.items():
87
- c.append_operation(f"C{p}", [2 * n, q.x])
87
+ c.append(f"C{p}", [2 * n, q.x])
88
88
  if post.coefficient == -1:
89
- c.append_operation("Z", [2 * n])
90
- c.append_operation("H", [2 * n])
91
- c.append_operation("M", [2 * n])
89
+ c.append("Z", [2 * n])
90
+ c.append("H", [2 * n])
91
+ c.append("M", [2 * n])
92
92
  correct = np.count_nonzero(c.compile_sampler().sample_bit_packed(10)) == 0
93
93
  assert correct, f"{gate!r} failed to turn {pre} into {post}.\nConverted to:\n{conv_gate}\n"
94
94
 
@@ -254,13 +254,13 @@ def test_cirq_circuit_to_stim_circuit_custom_stim_method():
254
254
  **kwargs,
255
255
  ):
256
256
  edit_measurement_key_lengths.append(("custom", 2))
257
- edit_circuit.append_operation("M", [stim.target_inv(targets[0])])
258
- edit_circuit.append_operation("M", [targets[0]])
259
- edit_circuit.append_operation("DETECTOR", [stim.target_rec(-1)])
257
+ edit_circuit.append("M", [stim.target_inv(targets[0])])
258
+ edit_circuit.append("M", [targets[0]])
259
+ edit_circuit.append("DETECTOR", [stim.target_rec(-1)])
260
260
 
261
261
  class SecondLastMeasurementWasDeterministicOperation(cirq.Operation):
262
- def _stim_conversion_(self, edit_circuit: stim.Circuit, **kwargs):
263
- edit_circuit.append_operation("DETECTOR", [stim.target_rec(-2)])
262
+ def _stim_conversion_(self, edit_circuit: stim.Circuit, tag: str, **kwargs):
263
+ edit_circuit.append("DETECTOR", [stim.target_rec(-2)], tag=tag)
264
264
 
265
265
  def with_qubits(self, *new_qubits):
266
266
  raise NotImplementedError()
@@ -392,3 +392,21 @@ def test_random_gate_channel():
392
392
  E(0.25) X1
393
393
  TICK
394
394
  """)
395
+
396
+
397
+ def test_custom_tagging():
398
+ assert stimcirq.cirq_circuit_to_stim_circuit(
399
+ cirq.Circuit(
400
+ cirq.X(cirq.LineQubit(0)).with_tags('test'),
401
+ cirq.X(cirq.LineQubit(0)).with_tags((2, 3, 4)),
402
+ cirq.H(cirq.LineQubit(0)).with_tags('a', 'b'),
403
+ ),
404
+ tag_func=lambda op: "PAIR" if len(op.tags) == 2 else repr(op.tags),
405
+ ) == stim.Circuit("""
406
+ X[('test',)] 0
407
+ TICK
408
+ X[((2, 3, 4),)] 0
409
+ TICK
410
+ H[PAIR] 0
411
+ TICK
412
+ """)
@@ -35,8 +35,8 @@ class CXSwapGate(cirq.Gate):
35
35
  yield cirq.CNOT(a, b)
36
36
  yield cirq.SWAP(a, b)
37
37
 
38
- def _stim_conversion_(self, edit_circuit: stim.Circuit, targets: List[int], **kwargs):
39
- edit_circuit.append_operation('SWAPCX' if self.inverted else 'CXSWAP', targets)
38
+ def _stim_conversion_(self, edit_circuit: stim.Circuit, targets: List[int], tag: str, **kwargs):
39
+ edit_circuit.append('SWAPCX' if self.inverted else 'CXSWAP', targets, tag=tag)
40
40
 
41
41
  def __pow__(self, power: int) -> 'CXSwapGate':
42
42
  if power == +1:
@@ -22,8 +22,8 @@ class CZSwapGate(cirq.Gate):
22
22
  yield cirq.SWAP(a, b)
23
23
  yield cirq.CZ(a, b)
24
24
 
25
- def _stim_conversion_(self, edit_circuit: stim.Circuit, targets: List[int], **kwargs):
26
- edit_circuit.append_operation('CZSWAP', targets)
25
+ def _stim_conversion_(self, edit_circuit: stim.Circuit, targets: List[int], tag: str, **kwargs):
26
+ edit_circuit.append('CZSWAP', targets, tag=tag)
27
27
 
28
28
  def __pow__(self, power: int) -> 'CZSwapGate':
29
29
  if power == +1:
@@ -77,8 +77,10 @@ class DetAnnotation(cirq.Operation):
77
77
 
78
78
  def _stim_conversion_(
79
79
  self,
80
+ *,
80
81
  edit_circuit: stim.Circuit,
81
82
  edit_measurement_key_lengths: List[Tuple[str, int]],
83
+ tag: str,
82
84
  have_seen_loop: bool = False,
83
85
  **kwargs,
84
86
  ):
@@ -111,4 +113,4 @@ class DetAnnotation(cirq.Operation):
111
113
  f" in an earlier moment (or earlier in the same moment's operation order)."
112
114
  )
113
115
 
114
- edit_circuit.append_operation("DETECTOR", rec_targets, self.coordinate_metadata)
116
+ edit_circuit.append("DETECTOR", rec_targets, self.coordinate_metadata, tag=tag)
@@ -91,15 +91,15 @@ class MeasureAndOrResetGate(cirq.Gate):
91
91
  result += self.basis
92
92
  return result
93
93
 
94
- def _stim_conversion_(self, edit_circuit: stim.Circuit, targets: List[int], **kwargs):
94
+ def _stim_conversion_(self, *, edit_circuit: stim.Circuit, targets: List[int], tag: str, **kwargs):
95
95
  if self.invert_measure:
96
96
  targets[0] = stim.target_inv(targets[0])
97
97
  if self.measure_flip_probability:
98
98
  edit_circuit.append_operation(
99
- self._stim_op_name(), targets, self.measure_flip_probability
99
+ self._stim_op_name(), targets, self.measure_flip_probability, tag=tag
100
100
  )
101
101
  else:
102
- edit_circuit.append_operation(self._stim_op_name(), targets)
102
+ edit_circuit.append_operation(self._stim_op_name(), targets, tag=tag)
103
103
 
104
104
  def __str__(self) -> str:
105
105
  result = self._stim_op_name()
@@ -75,9 +75,11 @@ class CumulativeObservableAnnotation(cirq.Operation):
75
75
 
76
76
  def _stim_conversion_(
77
77
  self,
78
+ *,
78
79
  edit_circuit: stim.Circuit,
79
80
  edit_measurement_key_lengths: List[Tuple[str, int]],
80
81
  have_seen_loop: bool = False,
82
+ tag: str,
81
83
  **kwargs,
82
84
  ):
83
85
  # Ideally these references would all be resolved ahead of time, to avoid the redundant
@@ -109,4 +111,4 @@ class CumulativeObservableAnnotation(cirq.Operation):
109
111
  f" in an earlier moment (or earlier in the same moment's operation order)."
110
112
  )
111
113
 
112
- edit_circuit.append_operation("OBSERVABLE_INCLUDE", rec_targets, self.observable_index)
114
+ edit_circuit.append("OBSERVABLE_INCLUDE", rec_targets, self.observable_index, tag=tag)
@@ -49,5 +49,5 @@ class ShiftCoordsAnnotation(cirq.Operation):
49
49
  def _is_comment_(self) -> bool:
50
50
  return True
51
51
 
52
- def _stim_conversion_(self, edit_circuit: stim.Circuit, **kwargs):
53
- edit_circuit.append_operation("SHIFT_COORDS", [], self.shift)
52
+ def _stim_conversion_(self, *, edit_circuit: stim.Circuit, tag: str, **kwargs):
53
+ edit_circuit.append_operation("SHIFT_COORDS", [], self.shift, tag=tag)
@@ -84,8 +84,12 @@ class CircuitTranslationTracker:
84
84
  m = cirq.num_qubits(gate)
85
85
  if not all(t.is_qubit_target for t in targets) or len(targets) % m != 0:
86
86
  raise NotImplementedError(f"instruction={instruction!r}")
87
+ if instruction.tag:
88
+ tags = [instruction.tag]
89
+ else:
90
+ tags = ()
87
91
  for k in range(0, len(targets), m):
88
- self.append_operation(gate(*[cirq.LineQubit(t.value) for t in targets[k : k + m]]))
92
+ self.append_operation(gate(*[cirq.LineQubit(t.value) for t in targets[k : k + m]]).with_tags(*tags))
89
93
 
90
94
  def process_tick(self, instruction: stim.CircuitInstruction) -> None:
91
95
  self.full_circuit += self.tick_circuit or cirq.Moment()
@@ -128,7 +132,7 @@ class CircuitTranslationTracker:
128
132
  self.process_gate_instruction(gate, instruction)
129
133
 
130
134
  def process_repeat_block(self, block: stim.CircuitRepeatBlock):
131
- if self.flatten or block.repeat_count == 1:
135
+ if self.flatten or (block.repeat_count == 1 and block.tag == ""):
132
136
  self.process_circuit(block.repeat_count, block.body_copy())
133
137
  return
134
138
 
@@ -141,6 +145,10 @@ class CircuitTranslationTracker:
141
145
  child.process_circuit(1, block.body_copy())
142
146
 
143
147
  # Circuit operation will always be in their own cirq.Moment
148
+ if block.tag == "":
149
+ tags = ()
150
+ else:
151
+ tags = (block.tag,)
144
152
  if len(self.tick_circuit):
145
153
  self.full_circuit += self.tick_circuit
146
154
  self.full_circuit += cirq.Moment(
@@ -148,7 +156,7 @@ class CircuitTranslationTracker:
148
156
  cirq.FrozenCircuit(child.full_circuit + child.tick_circuit),
149
157
  repetitions=block.repeat_count,
150
158
  use_repetition_ids=False,
151
- )
159
+ ).with_tags(*tags)
152
160
  )
153
161
  self.tick_circuit = cirq.Circuit()
154
162
 
@@ -168,6 +176,10 @@ class CircuitTranslationTracker:
168
176
  flip_probability = args[0]
169
177
 
170
178
  targets: List[stim.GateTarget] = instruction.targets_copy()
179
+ if instruction.tag:
180
+ tags = [instruction.tag]
181
+ else:
182
+ tags = ()
171
183
  for t in targets:
172
184
  if not t.is_qubit_target:
173
185
  raise NotImplementedError(f"instruction={instruction!r}")
@@ -180,7 +192,7 @@ class CircuitTranslationTracker:
180
192
  invert_measure=t.is_inverted_result_target,
181
193
  key=key,
182
194
  measure_flip_probability=flip_probability,
183
- ).resolve(cirq.LineQubit(t.value))
195
+ ).resolve(cirq.LineQubit(t.value)).with_tags(*tags)
184
196
  )
185
197
 
186
198
  def process_circuit(self, repetitions: int, circuit: stim.Circuit) -> None:
@@ -219,6 +231,10 @@ class CircuitTranslationTracker:
219
231
  raise NotImplementedError("Noisy MPP")
220
232
 
221
233
  targets: List[stim.GateTarget] = instruction.targets_copy()
234
+ if instruction.tag:
235
+ tags = [instruction.tag]
236
+ else:
237
+ tags = ()
222
238
  start = 0
223
239
  while start < len(targets):
224
240
  next_start = start + 1
@@ -230,13 +246,17 @@ class CircuitTranslationTracker:
230
246
  obs = _stim_targets_to_dense_pauli_string(group)
231
247
  qubits = [cirq.LineQubit(t.value) for t in group]
232
248
  key = str(self.get_next_measure_id())
233
- self.append_operation(cirq.PauliMeasurementGate(obs, key=key).on(*qubits))
249
+ self.append_operation(cirq.PauliMeasurementGate(obs, key=key).on(*qubits).with_tags(*tags))
234
250
 
235
251
  def process_spp_dag(self, instruction: stim.CircuitInstruction) -> None:
236
252
  self.process_spp(instruction, dag=True)
237
253
 
238
254
  def process_spp(self, instruction: stim.CircuitInstruction, dag: bool = False) -> None:
239
255
  targets: List[stim.GateTarget] = instruction.targets_copy()
256
+ if instruction.tag:
257
+ tags = [instruction.tag]
258
+ else:
259
+ tags = ()
240
260
  start = 0
241
261
  while start < len(targets):
242
262
  next_start = start + 1
@@ -250,13 +270,17 @@ class CircuitTranslationTracker:
250
270
  self.append_operation(cirq.PauliStringPhasorGate(
251
271
  obs,
252
272
  exponent_neg=-0.5 if dag else 0.5,
253
- ).on(*qubits))
273
+ ).on(*qubits).with_tags(*tags))
254
274
 
255
275
  def process_m_pair(self, instruction: stim.CircuitInstruction, basis: str) -> None:
256
276
  args = instruction.gate_args_copy()
257
277
  if args and args[0]:
258
278
  raise NotImplementedError("Noisy M" + basis*2)
259
279
 
280
+ if instruction.tag:
281
+ tags = [instruction.tag]
282
+ else:
283
+ tags = ()
260
284
  targets: List[stim.GateTarget] = instruction.targets_copy()
261
285
  for k in range(0, len(targets), 2):
262
286
  obs = cirq.DensePauliString(basis * 2)
@@ -264,7 +288,7 @@ class CircuitTranslationTracker:
264
288
  obs *= -1
265
289
  qubits = [cirq.LineQubit(targets[0].value), cirq.LineQubit(targets[1].value)]
266
290
  key = str(self.get_next_measure_id())
267
- self.append_operation(cirq.PauliMeasurementGate(obs, key=key).on(*qubits))
291
+ self.append_operation(cirq.PauliMeasurementGate(obs, key=key).on(*qubits).with_tags(*tags))
268
292
 
269
293
  def process_mxx(self, instruction: stim.CircuitInstruction) -> None:
270
294
  self.process_m_pair(instruction, "X")
@@ -286,12 +310,16 @@ class CircuitTranslationTracker:
286
310
  self.append_operation(cirq.PauliMeasurementGate(obs, key=key).on(*qubits))
287
311
 
288
312
  def process_correlated_error(self, instruction: stim.CircuitInstruction) -> None:
313
+ if instruction.tag:
314
+ tags = [instruction.tag]
315
+ else:
316
+ tags = ()
289
317
  args = instruction.gate_args_copy()
290
318
  probability = args[0] if args else 0
291
319
  targets = instruction.targets_copy()
292
320
  qubits = [cirq.LineQubit(t.value) for t in targets]
293
321
  self.append_operation(
294
- _stim_targets_to_dense_pauli_string(targets).on(*qubits).with_probability(probability)
322
+ _stim_targets_to_dense_pauli_string(targets).on(*qubits).with_probability(probability).with_tags(*tags)
295
323
  )
296
324
 
297
325
  def coords_after_offset(
@@ -316,20 +344,28 @@ class CircuitTranslationTracker:
316
344
  return [str(self.num_measurements_seen + t.value) for t in targets], []
317
345
 
318
346
  def process_detector(self, instruction: stim.CircuitInstruction) -> None:
347
+ if instruction.tag:
348
+ tags = [instruction.tag]
349
+ else:
350
+ tags = ()
319
351
  coords = self.coords_after_offset(instruction.gate_args_copy())
320
352
  keys, rels = self.resolve_measurement_record_keys(instruction.targets_copy())
321
353
  self.append_operation(
322
- DetAnnotation(parity_keys=keys, relative_keys=rels, coordinate_metadata=coords)
354
+ DetAnnotation(parity_keys=keys, relative_keys=rels, coordinate_metadata=coords).with_tags(*tags)
323
355
  )
324
356
 
325
357
  def process_observable_include(self, instruction: stim.CircuitInstruction) -> None:
358
+ if instruction.tag:
359
+ tags = [instruction.tag]
360
+ else:
361
+ tags = ()
326
362
  args = instruction.gate_args_copy()
327
363
  index = 0 if not args else int(args[0])
328
364
  keys, rels = self.resolve_measurement_record_keys(instruction.targets_copy())
329
365
  self.append_operation(
330
366
  CumulativeObservableAnnotation(
331
367
  parity_keys=keys, relative_keys=rels, observable_index=index
332
- )
368
+ ).with_tags(*tags)
333
369
  )
334
370
 
335
371
  def process_qubit_coords(self, instruction: stim.CircuitInstruction) -> None:
@@ -341,9 +377,13 @@ class CircuitTranslationTracker:
341
377
  self.qubit_coords[t.value] = cirq.GridQubit(*coords)
342
378
 
343
379
  def process_shift_coords(self, instruction: stim.CircuitInstruction) -> None:
380
+ if instruction.tag:
381
+ tags = [instruction.tag]
382
+ else:
383
+ tags = ()
344
384
  args = instruction.gate_args_copy()
345
385
  if not self.flatten:
346
- self.append_operation(ShiftCoordsAnnotation(args))
386
+ self.append_operation(ShiftCoordsAnnotation(args).with_tags(*tags))
347
387
  for k, a in enumerate(args):
348
388
  self.origin[k] += a
349
389
 
@@ -364,6 +404,10 @@ class CircuitTranslationTracker:
364
404
  def __call__(
365
405
  self, tracker: 'CircuitTranslationTracker', instruction: stim.CircuitInstruction
366
406
  ) -> None:
407
+ if instruction.tag:
408
+ tags = [instruction.tag]
409
+ else:
410
+ tags = ()
367
411
  targets: List[stim.GateTarget] = instruction.targets_copy()
368
412
  for k in range(0, len(targets), 2):
369
413
  a = targets[k]
@@ -379,13 +423,13 @@ class CircuitTranslationTracker:
379
423
  stim_sweep_bit_index=a.value,
380
424
  cirq_sweep_symbol=f'sweep[{a.value}]',
381
425
  pauli=self.pauli_gate,
382
- ).on(cirq.LineQubit(b.value))
426
+ ).on(cirq.LineQubit(b.value)).with_tags(*tags)
383
427
  )
384
428
  else:
385
429
  if not a.is_qubit_target or not b.is_qubit_target:
386
430
  raise NotImplementedError(f"instruction={instruction!r}")
387
431
  tracker.append_operation(
388
- self.gate(cirq.LineQubit(a.value), cirq.LineQubit(b.value))
432
+ self.gate(cirq.LineQubit(a.value), cirq.LineQubit(b.value)).with_tags(*tags)
389
433
  )
390
434
 
391
435
  class OneToOneMeasurementHandler:
@@ -422,7 +466,7 @@ class CircuitTranslationTracker:
422
466
  noise = CircuitTranslationTracker.OneToOneNoisyGateHandler
423
467
  sweep_gate = CircuitTranslationTracker.SweepableGateHandler
424
468
 
425
- def not_impl(message) -> Callable[[Any], None]:
469
+ def not_impl(message) -> Callable[[Any, Any], None]:
426
470
  def handler(
427
471
  tracker: CircuitTranslationTracker, instruction: stim.CircuitInstruction
428
472
  ) -> None:
@@ -682,3 +682,72 @@ def test_stim_circuit_to_cirq_circuit_spp():
682
682
  SPP Z0
683
683
  TICK
684
684
  """)
685
+
686
+
687
+ def test_tags_convert():
688
+ assert stimcirq.stim_circuit_to_cirq_circuit(stim.Circuit("""
689
+ H[my_tag] 0
690
+ """)) == cirq.Circuit(
691
+ cirq.H(cirq.LineQubit(0)).with_tags('my_tag'),
692
+ )
693
+
694
+
695
+ @pytest.mark.parametrize('gate', sorted(stim.gate_data().keys()))
696
+ def test_every_operation_converts_tags(gate: str):
697
+ if gate in [
698
+ "ELSE_CORRELATED_ERROR",
699
+ "HERALDED_ERASE",
700
+ "HERALDED_PAULI_CHANNEL_1",
701
+ "TICK",
702
+ "REPEAT",
703
+ "MPAD",
704
+ "QUBIT_COORDS",
705
+ ]:
706
+ pytest.skip()
707
+
708
+ data = stim.gate_data(gate)
709
+ stim_circuit = stim.Circuit()
710
+ arg = None
711
+ targets = [0, 1]
712
+ if data.num_parens_arguments_range.start:
713
+ arg = [2**-6] * data.num_parens_arguments_range.start
714
+ if data.takes_pauli_targets:
715
+ targets = [stim.target_x(0), stim.target_y(1)]
716
+ if data.takes_measurement_record_targets and not data.is_unitary:
717
+ stim_circuit.append("M", [0], tag='custom_tag')
718
+ targets = [stim.target_rec(-1)]
719
+ if gate == 'SHIFT_COORDS':
720
+ targets = []
721
+ if gate == 'OBSERVABLE_INCLUDE':
722
+ arg = [1]
723
+ stim_circuit.append(gate, targets, arg, tag='custom_tag')
724
+ cirq_circuit = stimcirq.stim_circuit_to_cirq_circuit(stim_circuit)
725
+ assert any(cirq_circuit.all_operations())
726
+ for op in cirq_circuit.all_operations():
727
+ assert op.tags == ('custom_tag',)
728
+ restored_circuit = stimcirq.cirq_circuit_to_stim_circuit(cirq_circuit)
729
+ assert restored_circuit.pop() == stim.CircuitInstruction("TICK")
730
+ assert all(instruction.tag == 'custom_tag' for instruction in restored_circuit)
731
+ if gate not in ['MXX', 'MYY', 'MZZ']:
732
+ assert restored_circuit == stim_circuit
733
+
734
+
735
+ def test_loop_tagging():
736
+ stim_circuit = stim.Circuit("""
737
+ REPEAT[custom-tag] 5 {
738
+ H[tag2] 0
739
+ TICK
740
+ }
741
+ """)
742
+ cirq_circuit = stimcirq.stim_circuit_to_cirq_circuit(stim_circuit)
743
+ assert cirq_circuit == cirq.Circuit(
744
+ cirq.CircuitOperation(
745
+ cirq.FrozenCircuit(
746
+ cirq.H(cirq.LineQubit(0)).with_tags('tag2'),
747
+ ),
748
+ repetitions=5,
749
+ use_repetition_ids=False,
750
+ ).with_tags('custom-tag')
751
+ )
752
+ restored_circuit = stimcirq.cirq_circuit_to_stim_circuit(cirq_circuit)
753
+ assert restored_circuit == stim_circuit
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stimcirq
3
- Version: 1.15.dev1733303566
3
+ Version: 1.15.dev1733420295
4
4
  Summary: Implements a cirq.Sampler backed by stim.
5
5
  Home-page: https://github.com/quantumlib/stim
6
6
  Author: Craig Gidney