cirq-core 1.5.0.dev20240920235700__py3-none-any.whl → 1.5.0.dev20240924062245__py3-none-any.whl

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.

Potentially problematic release.


This version of cirq-core might be problematic. Click here for more details.

cirq/_version.py CHANGED
@@ -28,4 +28,4 @@ if sys.version_info < (3, 10, 0): # pragma: no cover
28
28
  'of cirq (e.g. "python -m pip install cirq==1.1.*")'
29
29
  )
30
30
 
31
- __version__ = "1.5.0.dev20240920235700"
31
+ __version__ = "1.5.0.dev20240924062245"
cirq/_version_test.py CHANGED
@@ -3,4 +3,4 @@ import cirq
3
3
 
4
4
 
5
5
  def test_version():
6
- assert cirq.__version__ == "1.5.0.dev20240920235700"
6
+ assert cirq.__version__ == "1.5.0.dev20240924062245"
@@ -15,41 +15,47 @@
15
15
  """Transformer pass that adds dynamical decoupling operations to a circuit."""
16
16
 
17
17
  from functools import reduce
18
- from typing import Dict, Optional, Tuple, Union
18
+ from typing import Dict, Optional, Tuple, Union, TYPE_CHECKING
19
19
  from itertools import cycle
20
20
 
21
+ from cirq import ops, circuits, protocols
21
22
  from cirq.transformers import transformer_api
22
23
  from cirq.transformers.analytical_decompositions import single_qubit_decompositions
23
- from cirq.transformers.analytical_decompositions import unitary_to_pauli_string
24
- import cirq
24
+ from cirq.protocols import unitary_protocol
25
+ from cirq.protocols.has_unitary_protocol import has_unitary
26
+ from cirq.protocols.has_stabilizer_effect_protocol import has_stabilizer_effect
27
+
25
28
  import numpy as np
26
29
 
30
+ if TYPE_CHECKING:
31
+ import cirq
32
+
27
33
 
28
- def _get_dd_sequence_from_schema_name(schema: str) -> Tuple['cirq.Gate', ...]:
34
+ def _get_dd_sequence_from_schema_name(schema: str) -> Tuple[ops.Gate, ...]:
29
35
  """Gets dynamical decoupling sequence from a schema name."""
30
36
  match schema:
31
37
  case 'DEFAULT':
32
- return (cirq.X, cirq.Y, cirq.X, cirq.Y)
38
+ return (ops.X, ops.Y, ops.X, ops.Y)
33
39
  case 'XX_PAIR':
34
- return (cirq.X, cirq.X)
40
+ return (ops.X, ops.X)
35
41
  case 'X_XINV':
36
- return (cirq.X, cirq.X**-1)
42
+ return (ops.X, ops.X**-1)
37
43
  case 'YY_PAIR':
38
- return (cirq.Y, cirq.Y)
44
+ return (ops.Y, ops.Y)
39
45
  case 'Y_YINV':
40
- return (cirq.Y, cirq.Y**-1)
46
+ return (ops.Y, ops.Y**-1)
41
47
  case _:
42
48
  raise ValueError('Invalid schema name.')
43
49
 
44
50
 
45
- def _pauli_up_to_global_phase(gate: 'cirq.Gate') -> Union['cirq.Pauli', None]:
46
- for pauli_gate in [cirq.X, cirq.Y, cirq.Z]:
47
- if cirq.equal_up_to_global_phase(gate, pauli_gate):
51
+ def _pauli_up_to_global_phase(gate: ops.Gate) -> Union[ops.Pauli, None]:
52
+ for pauli_gate in [ops.X, ops.Y, ops.Z]:
53
+ if protocols.equal_up_to_global_phase(gate, pauli_gate):
48
54
  return pauli_gate
49
55
  return None
50
56
 
51
57
 
52
- def _validate_dd_sequence(dd_sequence: Tuple['cirq.Gate', ...]) -> None:
58
+ def _validate_dd_sequence(dd_sequence: Tuple[ops.Gate, ...]) -> None:
53
59
  """Validates a given dynamical decoupling sequence.
54
60
 
55
61
  Args:
@@ -66,144 +72,132 @@ def _validate_dd_sequence(dd_sequence: Tuple['cirq.Gate', ...]) -> None:
66
72
  'Dynamical decoupling sequence should only contain gates that are essentially'
67
73
  ' Pauli gates.'
68
74
  )
69
- matrices = [cirq.unitary(gate) for gate in dd_sequence]
75
+ matrices = [unitary_protocol.unitary(gate) for gate in dd_sequence]
70
76
  product = reduce(np.matmul, matrices)
71
77
 
72
- if not cirq.equal_up_to_global_phase(product, np.eye(2)):
78
+ if not protocols.equal_up_to_global_phase(product, np.eye(2)):
73
79
  raise ValueError(
74
80
  'Invalid dynamical decoupling sequence. Expect sequence production equals'
75
81
  f' identity up to a global phase, got {product}.'.replace('\n', ' ')
76
82
  )
77
83
 
78
84
 
79
- def _parse_dd_sequence(schema: Union[str, Tuple['cirq.Gate', ...]]) -> Tuple['cirq.Gate', ...]:
80
- """Parses and returns dynamical decoupling sequence from schema."""
85
+ def _parse_dd_sequence(
86
+ schema: Union[str, Tuple[ops.Gate, ...]]
87
+ ) -> Tuple[Tuple[ops.Gate, ...], Dict[ops.Gate, ops.Pauli]]:
88
+ """Parses and returns dynamical decoupling sequence and its associated pauli map from schema."""
89
+ dd_sequence = None
81
90
  if isinstance(schema, str):
82
- return _get_dd_sequence_from_schema_name(schema)
91
+ dd_sequence = _get_dd_sequence_from_schema_name(schema)
83
92
  else:
84
93
  _validate_dd_sequence(schema)
85
- return schema
94
+ dd_sequence = schema
86
95
 
96
+ # Map Gate to Puali gate. This is necessary as dd sequence might contain gates like X^-1.
97
+ pauli_map: Dict[ops.Gate, ops.Pauli] = {}
98
+ for gate in dd_sequence:
99
+ pauli_gate = _pauli_up_to_global_phase(gate)
100
+ if pauli_gate is not None:
101
+ pauli_map[gate] = pauli_gate
102
+ for gate in [ops.X, ops.Y, ops.Z]:
103
+ pauli_map[gate] = gate
87
104
 
88
- def _is_single_qubit_operation(operation: 'cirq.Operation') -> bool:
89
- if len(operation.qubits) != 1:
90
- return False
91
- return True
105
+ return (dd_sequence, pauli_map)
106
+
107
+
108
+ def _is_single_qubit_operation(operation: ops.Operation) -> bool:
109
+ return len(operation.qubits) == 1
92
110
 
93
111
 
94
- def _is_single_qubit_gate_moment(moment: 'cirq.Moment') -> bool:
95
- for operation in moment:
96
- if not _is_single_qubit_operation(operation):
97
- return False
98
- return True
112
+ def _is_single_qubit_gate_moment(moment: circuits.Moment) -> bool:
113
+ return all(_is_single_qubit_operation(op) for op in moment)
99
114
 
100
115
 
101
- def _is_clifford_moment(moment: 'cirq.Moment') -> bool:
102
- for op in moment.operations:
103
- if op.gate is not None and isinstance(op.gate, cirq.MeasurementGate):
104
- return False
105
- if not cirq.has_stabilizer_effect(op):
106
- return False
107
- return True
116
+ def _is_clifford_op(op: ops.Operation) -> bool:
117
+ return has_unitary(op) and has_stabilizer_effect(op)
108
118
 
109
119
 
110
- def _get_clifford_pieces(circuit: 'cirq.AbstractCircuit') -> list[Tuple[int, int]]:
111
- clifford_pieces: list[Tuple[int, int]] = []
112
- left = 0
120
+ def _calc_busy_moment_range_of_each_qubit(
121
+ circuit: circuits.FrozenCircuit,
122
+ ) -> Dict[ops.Qid, list[int]]:
123
+ busy_moment_range_by_qubit: Dict[ops.Qid, list[int]] = {
124
+ q: [len(circuit), -1] for q in circuit.all_qubits()
125
+ }
113
126
  for moment_id, moment in enumerate(circuit):
114
- if not _is_clifford_moment(moment):
115
- clifford_pieces.append((left, moment_id))
116
- left = moment_id + 1
117
- if left < len(circuit):
118
- clifford_pieces.append((left, len(circuit)))
119
- return clifford_pieces
127
+ for q in moment.qubits:
128
+ busy_moment_range_by_qubit[q][0] = min(busy_moment_range_by_qubit[q][0], moment_id)
129
+ busy_moment_range_by_qubit[q][1] = max(busy_moment_range_by_qubit[q][1], moment_id)
130
+ return busy_moment_range_by_qubit
120
131
 
121
132
 
122
- def _is_insertable_moment(moment: 'cirq.Moment', single_qubit_gate_moments_only: bool) -> bool:
123
- return _is_single_qubit_gate_moment(moment) or not single_qubit_gate_moments_only
133
+ def _is_insertable_moment(moment: circuits.Moment, single_qubit_gate_moments_only: bool) -> bool:
134
+ return not single_qubit_gate_moments_only or _is_single_qubit_gate_moment(moment)
124
135
 
125
136
 
126
- def _calc_pulled_through(
127
- moment: 'cirq.Moment', input_pauli_ops: 'cirq.PauliString'
128
- ) -> 'cirq.PauliString':
129
- """Calculates the pulled_through after pulling through moment with the input.
137
+ def _merge_single_qubit_ops_to_phxz(
138
+ q: ops.Qid, operations: Tuple[ops.Operation, ...]
139
+ ) -> ops.Operation:
140
+ """Merges [op1, op2, ...] and returns an equivalent op"""
141
+ if len(operations) == 1:
142
+ return operations[0]
143
+ matrices = [unitary_protocol.unitary(op) for op in reversed(operations)]
144
+ product = reduce(np.matmul, matrices)
145
+ gate = single_qubit_decompositions.single_qubit_matrix_to_phxz(product) or ops.I
146
+ return gate.on(q)
147
+
148
+
149
+ def _try_merge_single_qubit_ops_of_two_moments(
150
+ m1: circuits.Moment, m2: circuits.Moment
151
+ ) -> Tuple[circuits.Moment, ...]:
152
+ """Merge single qubit ops of 2 moments if possible, returns 2 moments otherwise."""
153
+ for q in m1.qubits & m2.qubits:
154
+ op1 = m1.operation_at(q)
155
+ op2 = m2.operation_at(q)
156
+ if any(
157
+ not (_is_single_qubit_operation(op) and has_unitary(op))
158
+ for op in [op1, op2]
159
+ if op is not None
160
+ ):
161
+ return (m1, m2)
162
+ merged_ops: set[ops.Operation] = set()
163
+ # Merge all operators on q to a single op.
164
+ for q in m1.qubits | m2.qubits:
165
+ # ops_on_q may contain 1 op or 2 ops.
166
+ ops_on_q = [op for op in [m.operation_at(q) for m in [m1, m2]] if op is not None]
167
+ merged_ops.add(_merge_single_qubit_ops_to_phxz(q, tuple(ops_on_q)))
168
+ return (circuits.Moment(merged_ops),)
130
169
 
131
- We assume that the moment is Clifford here. Then, pulling through is essentially
132
- decomposing a matrix into Pauli operations on each qubit.
133
- """
134
- pulled_through: 'cirq.PauliString' = cirq.PauliString()
135
- for affected_q, combined_op_in_pauli in input_pauli_ops.items():
136
- op_at_moment = moment.operation_at(affected_q)
137
- if op_at_moment is None:
138
- pulled_through *= combined_op_in_pauli.on(affected_q)
139
- continue
140
- prev_circuit = cirq.Circuit(cirq.Moment(op_at_moment))
141
- new_circuit = cirq.Circuit(
142
- cirq.Moment(combined_op_in_pauli.on(affected_q)), cirq.Moment(op_at_moment)
143
- )
144
- qubit_order = op_at_moment.qubits
145
- pulled_through_pauli_ops = unitary_to_pauli_string(
146
- prev_circuit.unitary(qubit_order=qubit_order)
147
- @ new_circuit.unitary(qubit_order=qubit_order).conj().T
148
- )
149
- if pulled_through_pauli_ops is not None:
150
- for qid, gate in enumerate(pulled_through_pauli_ops):
151
- pulled_through *= gate.on(qubit_order[qid])
152
- return pulled_through
153
-
154
-
155
- def _merge_pulled_through(
156
- mutable_circuit: 'cirq.Circuit',
157
- pulled_through: 'cirq.PauliString',
158
- clifford_piece_range: Tuple[int, int],
159
- single_qubit_gate_moments_only: bool,
160
- ) -> 'cirq.PauliString':
161
- """Merges pulled through Pauli gates into the last single-qubit gate operation or the insert it
162
- into the first idle moment if idle moments exist.
163
- Args:
164
- mutable_circuit: Mutable circuit to transform.
165
- pulled_through: Pauli gates to be merged.
166
- clifford_piece_range: Specifies the [l, r) moments within which pulled-through gate merging
167
- is to be performed.
168
- single_qubit_gate_moments_only: If set True, dynamical decoupling operation will only be
169
- added in single-qubit gate moments.
170
170
 
171
- Returns:
172
- The remaining pulled through operations after merging.
171
+ def _calc_pulled_through(
172
+ moment: circuits.Moment, input_pauli_ops: ops.PauliString
173
+ ) -> ops.PauliString:
174
+ """Calculates the pulled_through such that circuit(input_puali_ops, moment.clifford_ops) is
175
+ equivalent to circuit(moment.clifford_ops, pulled_through).
173
176
  """
174
- insert_intos: list[Tuple[int, 'cirq.Operation']] = []
175
- batch_replaces: list[Tuple[int, 'cirq.Operation', 'cirq.Operation']] = []
176
- remaining_pulled_through = pulled_through
177
- for affected_q, combined_op_in_pauli in pulled_through.items():
178
- moment_id = mutable_circuit.prev_moment_operating_on([affected_q], clifford_piece_range[1])
179
- if moment_id is not None:
180
- op = mutable_circuit.operation_at(affected_q, moment_id)
181
- # Try to merge op into an existing single-qubit gate operation.
182
- if op is not None and _is_single_qubit_operation(op):
183
- updated_gate_mat = cirq.unitary(combined_op_in_pauli) @ cirq.unitary(op)
184
- updated_gate: Optional['cirq.Gate'] = (
185
- single_qubit_decompositions.single_qubit_matrix_to_phxz(updated_gate_mat)
186
- )
187
- if updated_gate is None:
188
- # updated_gate is close to Identity.
189
- updated_gate = cirq.I
190
- batch_replaces.append((moment_id, op, updated_gate.on(affected_q)))
191
- remaining_pulled_through *= combined_op_in_pauli.on(affected_q)
192
- continue
193
- # Insert into the first empty moment for the qubit if such moment exists.
194
- while moment_id < clifford_piece_range[1]:
195
- if affected_q not in mutable_circuit.moments[
196
- moment_id
197
- ].qubits and _is_insertable_moment(
198
- mutable_circuit.moments[moment_id], single_qubit_gate_moments_only
199
- ):
200
- insert_intos.append((moment_id, combined_op_in_pauli.on(affected_q)))
201
- remaining_pulled_through *= combined_op_in_pauli.on(affected_q)
202
- break
203
- moment_id += 1
204
- mutable_circuit.batch_insert_into(insert_intos)
205
- mutable_circuit.batch_replace(batch_replaces)
206
- return remaining_pulled_through
177
+ clifford_ops_in_moment: list[ops.Operation] = [
178
+ op for op in moment.operations if _is_clifford_op(op)
179
+ ]
180
+ return input_pauli_ops.after(clifford_ops_in_moment)
181
+
182
+
183
+ def _get_stop_qubits(moment: circuits.Moment) -> set[ops.Qid]:
184
+ stop_pulling_through_qubits: set[ops.Qid] = set()
185
+ for op in moment:
186
+ if (not _is_clifford_op(op) and not _is_single_qubit_operation(op)) or not has_unitary(
187
+ op
188
+ ): # multi-qubit clifford op or non-mergable op.
189
+ stop_pulling_through_qubits.update(op.qubits)
190
+ return stop_pulling_through_qubits
191
+
192
+
193
+ def _need_merge_pulled_through(op_at_q: ops.Operation, is_at_last_busy_moment: bool) -> bool:
194
+ """With a pulling through puali gate before op_at_q, need to merge with the
195
+ pauli in the conditions below."""
196
+ # The op must be mergable and single-qubit
197
+ if not (_is_single_qubit_operation(op_at_q) and has_unitary(op_at_q)):
198
+ return False
199
+ # Either non-Clifford or at the last busy moment
200
+ return is_at_last_busy_moment or not _is_clifford_op(op_at_q)
207
201
 
208
202
 
209
203
  @transformer_api.transformer
@@ -211,12 +205,11 @@ def add_dynamical_decoupling(
211
205
  circuit: 'cirq.AbstractCircuit',
212
206
  *,
213
207
  context: Optional['cirq.TransformerContext'] = None,
214
- schema: Union[str, Tuple['cirq.Gate', ...]] = 'DEFAULT',
208
+ schema: Union[str, Tuple[ops.Gate, ...]] = 'DEFAULT',
215
209
  single_qubit_gate_moments_only: bool = True,
216
210
  ) -> 'cirq.Circuit':
217
211
  """Adds dynamical decoupling gate operations to a given circuit.
218
- This transformer might add a new moment after each piece of Clifford moments, so the original
219
- moment structure could change.
212
+ This transformer might add new moments thus change structure of the original circuit.
220
213
 
221
214
  Args:
222
215
  circuit: Input circuit to transform.
@@ -230,64 +223,110 @@ def add_dynamical_decoupling(
230
223
  Returns:
231
224
  A copy of the input circuit with dynamical decoupling operations.
232
225
  """
233
- base_dd_sequence: Tuple['cirq.Gate', ...] = _parse_dd_sequence(schema)
234
- mutable_circuit = circuit.unfreeze(copy=True)
226
+ base_dd_sequence, pauli_map = _parse_dd_sequence(schema)
227
+ orig_circuit = circuit.freeze()
228
+
229
+ busy_moment_range_by_qubit = _calc_busy_moment_range_of_each_qubit(orig_circuit)
230
+
231
+ # Stores all the moments of the output circuit chronically
232
+ transformed_moments: list[circuits.Moment] = []
233
+ # A PauliString stores the result of 'pulling' Pauli gates past each operations
234
+ # right before the current moment.
235
+ pulled_through: ops.PauliString = ops.PauliString()
236
+ # Iterator of gate to be used in dd sequence for each qubit.
237
+ dd_iter_by_qubits = {q: cycle(base_dd_sequence) for q in circuit.all_qubits()}
238
+
239
+ def _update_pulled_through(q: ops.Qid, insert_gate: ops.Gate) -> ops.Operation:
240
+ nonlocal pulled_through, pauli_map
241
+ pulled_through *= pauli_map[insert_gate].on(q)
242
+ return insert_gate.on(q)
243
+
244
+ # Insert and pull remaining Puali ops through the whole circuit.
245
+ # General ideas are
246
+ # * Pull through Clifford gates.
247
+ # * Stop at multi-qubit non-Clifford ops (and other non-mergable ops).
248
+ # * Merge to single-qubit non-Clifford ops.
249
+ # * Insert a new moment if necessary.
250
+ # After pulling through pulled_through at `moment`, we expect a transformation of
251
+ # (pulled_through, moment) -> (updated_moment, updated_pulled_through) or
252
+ # (pulled_through, moment) -> (new_moment, updated_moment, updated_pulled_through)
253
+ # Moments structure changes are split into 3 steps:
254
+ # 1, (..., last_moment, pulled_through1, moment, ...)
255
+ # -> (..., try_merge(last_moment, new_moment or None), pulled_through2, moment, ...)
256
+ # 2, (..., pulled_through2, moment, ...) -> (..., pulled_through3, updated_moment, ...)
257
+ # 3, (..., pulled_through3, updated_moment, ...)
258
+ # -> (..., updated_moment, pulled_through4, ...)
259
+ for moment_id, moment in enumerate(orig_circuit.moments):
260
+ # Step 1, insert new_moment if necessary.
261
+ # In detail: stop pulling through for multi-qubit non-Clifford ops or gates without
262
+ # unitary representation (e.g., measure gates). If there are remaining pulled through ops,
263
+ # insert into a new moment before current moment.
264
+ stop_pulling_through_qubits: set[ops.Qid] = _get_stop_qubits(moment)
265
+ new_moment_ops = []
266
+ for q in stop_pulling_through_qubits:
267
+ # Insert the remaining pulled_through
268
+ remaining_pulled_through_gate = pulled_through.get(q)
269
+ if remaining_pulled_through_gate is not None:
270
+ new_moment_ops.append(_update_pulled_through(q, remaining_pulled_through_gate))
271
+ # Reset dd sequence
272
+ dd_iter_by_qubits[q] = cycle(base_dd_sequence)
273
+ # Need to insert a new moment before current moment
274
+ if new_moment_ops:
275
+ # Fill insertable idle moments in the new moment using dd sequence
276
+ for q in orig_circuit.all_qubits() - stop_pulling_through_qubits:
277
+ if busy_moment_range_by_qubit[q][0] < moment_id <= busy_moment_range_by_qubit[q][1]:
278
+ new_moment_ops.append(_update_pulled_through(q, next(dd_iter_by_qubits[q])))
279
+ moments_to_be_appended = _try_merge_single_qubit_ops_of_two_moments(
280
+ transformed_moments.pop(), circuits.Moment(new_moment_ops)
281
+ )
282
+ transformed_moments.extend(moments_to_be_appended)
283
+
284
+ # Step 2, calc updated_moment with insertions / merges.
285
+ updated_moment_ops: set['cirq.Operation'] = set()
286
+ for q in orig_circuit.all_qubits():
287
+ op_at_q = moment.operation_at(q)
288
+ remaining_pulled_through_gate = pulled_through.get(q)
289
+ updated_op = op_at_q
290
+ if op_at_q is None: # insert into idle op
291
+ if not _is_insertable_moment(moment, single_qubit_gate_moments_only):
292
+ continue
293
+ if (
294
+ busy_moment_range_by_qubit[q][0] < moment_id < busy_moment_range_by_qubit[q][1]
295
+ ): # insert next pauli gate in the dd sequence
296
+ updated_op = _update_pulled_through(q, next(dd_iter_by_qubits[q]))
297
+ elif ( # insert the remaining pulled through if beyond the ending busy moment
298
+ moment_id > busy_moment_range_by_qubit[q][1]
299
+ and remaining_pulled_through_gate is not None
300
+ ):
301
+ updated_op = _update_pulled_through(q, remaining_pulled_through_gate)
302
+ elif (
303
+ remaining_pulled_through_gate is not None
304
+ ): # merge pulled-through of q to op_at_q if needed
305
+ if _need_merge_pulled_through(
306
+ op_at_q, moment_id == busy_moment_range_by_qubit[q][1]
307
+ ):
308
+ remaining_op = _update_pulled_through(q, remaining_pulled_through_gate)
309
+ updated_op = _merge_single_qubit_ops_to_phxz(q, (remaining_op, op_at_q))
310
+ if updated_op is not None:
311
+ updated_moment_ops.add(updated_op)
235
312
 
236
- pauli_map: Dict['cirq.Gate', 'cirq.Pauli'] = {}
237
- for gate in base_dd_sequence:
238
- pauli_gate = _pauli_up_to_global_phase(gate)
239
- if pauli_gate is not None:
240
- pauli_map[gate] = pauli_gate
313
+ if updated_moment_ops:
314
+ updated_moment = circuits.Moment(updated_moment_ops)
315
+ transformed_moments.append(updated_moment)
241
316
 
242
- busy_moment_range_by_qubit: Dict['cirq.Qid', list[int]] = {
243
- q: [len(circuit), -1] for q in circuit.all_qubits()
244
- }
245
- for moment_id, moment in enumerate(circuit):
246
- for q in moment.qubits:
247
- busy_moment_range_by_qubit[q][0] = min(busy_moment_range_by_qubit[q][0], moment_id)
248
- busy_moment_range_by_qubit[q][1] = max(busy_moment_range_by_qubit[q][1], moment_id)
249
- clifford_pieces = _get_clifford_pieces(circuit)
250
-
251
- insert_intos: list[Tuple[int, 'cirq.Operation']] = []
252
- insert_moments: list[Tuple[int, 'cirq.Moment']] = []
253
- for l, r in clifford_pieces: # [l, r)
254
- # A PauliString stores the result of 'pulling' Pauli gates past each operations
255
- # right before the current moment.
256
- pulled_through: 'cirq.PauliString' = cirq.PauliString()
257
- iter_by_qubits = {q: cycle(base_dd_sequence) for q in circuit.all_qubits()}
258
-
259
- # Iterate over the Clifford piece.
260
- for moment_id in range(l, r):
261
- moment = circuit.moments[moment_id]
262
-
263
- # Insert
264
- if _is_insertable_moment(moment, single_qubit_gate_moments_only):
265
- for q in circuit.all_qubits() - moment.qubits:
266
- if (
267
- busy_moment_range_by_qubit[q][0]
268
- < moment_id
269
- < busy_moment_range_by_qubit[q][1]
270
- ):
271
- insert_gate = next(iter_by_qubits[q])
272
- insert_intos.append((moment_id, insert_gate.on(q)))
273
- pulled_through *= pauli_map[insert_gate].on(q)
274
-
275
- # Pull through
276
- pulled_through = _calc_pulled_through(moment, pulled_through)
277
-
278
- mutable_circuit.batch_insert_into(insert_intos)
279
- insert_intos.clear()
280
-
281
- pulled_through = _merge_pulled_through(
282
- mutable_circuit, pulled_through, (l, r), single_qubit_gate_moments_only
283
- )
317
+ # Step 3, update pulled through.
318
+ # In detail: pulling current `pulled_through` through updated_moment.
319
+ pulled_through = _calc_pulled_through(updated_moment, pulled_through)
284
320
 
285
- # Insert a new moment if there are remaining pulled through operations.
286
- new_moment_ops = []
287
- for affected_q, combined_op_in_pauli in pulled_through.items():
288
- new_moment_ops.append(combined_op_in_pauli.on(affected_q))
289
- if len(new_moment_ops) != 0:
290
- insert_moments.append((r, cirq.Moment(new_moment_ops)))
321
+ # Insert a new moment if there are remaining pulled-through operations.
322
+ ending_moment_ops = []
323
+ for affected_q, combined_op_in_pauli in pulled_through.items():
324
+ ending_moment_ops.append(combined_op_in_pauli.on(affected_q))
325
+ if ending_moment_ops:
326
+ transformed_moments.extend(
327
+ _try_merge_single_qubit_ops_of_two_moments(
328
+ transformed_moments.pop(), circuits.Moment(ending_moment_ops)
329
+ )
330
+ )
291
331
 
292
- mutable_circuit.batch_insert(insert_moments)
293
- return mutable_circuit
332
+ return circuits.Circuit(transformed_moments)
@@ -30,14 +30,17 @@ def assert_sim_eq(circuit1: 'cirq.AbstractCircuit', circuit2: 'cirq.AbstractCirc
30
30
 
31
31
  def assert_dd(
32
32
  input_circuit: 'cirq.AbstractCircuit',
33
- expected_circuit: 'cirq.AbstractCircuit',
33
+ expected_circuit: Union[str, 'cirq.AbstractCircuit'],
34
34
  schema: Union[str, Tuple['cirq.Gate', ...]] = 'DEFAULT',
35
35
  single_qubit_gate_moments_only: bool = True,
36
36
  ):
37
37
  transformed_circuit = add_dynamical_decoupling(
38
38
  input_circuit, schema=schema, single_qubit_gate_moments_only=single_qubit_gate_moments_only
39
39
  ).freeze()
40
- cirq.testing.assert_same_circuits(transformed_circuit, expected_circuit)
40
+ if isinstance(expected_circuit, str):
41
+ cirq.testing.assert_has_diagram(transformed_circuit, expected_circuit)
42
+ else:
43
+ cirq.testing.assert_same_circuits(transformed_circuit, expected_circuit)
41
44
  cirq.testing.assert_circuits_have_same_unitary_given_final_permutation(
42
45
  cirq.drop_terminal_measurements(input_circuit),
43
46
  cirq.drop_terminal_measurements(transformed_circuit),
@@ -533,7 +536,7 @@ def test_pull_through_chain():
533
536
  )
534
537
 
535
538
 
536
- def test_multiple_clifford_pieces():
539
+ def test_multiple_clifford_pieces_case1():
537
540
  """Test case diagrams.
538
541
  Input:
539
542
  a: ───H───────H───────@───────────H───────H───
@@ -579,6 +582,83 @@ def test_multiple_clifford_pieces():
579
582
  )
580
583
 
581
584
 
585
+ def test_multiple_clifford_pieces_case2():
586
+ """Test case diagrams.
587
+ Input:
588
+ a: ───@───PhXZ(a=0.3,x=0.2,z=0)───PhXZ(a=0.3,x=0.2,z=0)───PhXZ(a=0.3,x=0.2,z=0)───@───
589
+ │ │
590
+ b: ───@───────────────────────────────────────────────────────────────────────────@───
591
+ Output:
592
+ a: ───@───PhXZ(a=0.3,x=0.2,z=0)───PhXZ(a=0.3,x=0.2,z=0)───PhXZ(a=0.3,x=0.2,z=0)───@───Z───
593
+ │ │
594
+ b: ───@───X───────────────────────X───────────────────────X───────────────────────@───X───
595
+ """
596
+ a = cirq.NamedQubit('a')
597
+ b = cirq.NamedQubit('b')
598
+ phased_xz_gate = cirq.PhasedXZGate(axis_phase_exponent=0.3, x_exponent=0.2, z_exponent=0)
599
+
600
+ assert_dd(
601
+ input_circuit=cirq.Circuit(
602
+ cirq.Moment(cirq.CZ(a, b)),
603
+ cirq.Moment(phased_xz_gate.on(a)),
604
+ cirq.Moment(phased_xz_gate.on(a)),
605
+ cirq.Moment(phased_xz_gate.on(a)),
606
+ cirq.Moment(cirq.CZ(a, b)),
607
+ ),
608
+ expected_circuit=cirq.Circuit(
609
+ cirq.Moment(cirq.CZ(a, b)),
610
+ cirq.Moment(phased_xz_gate.on(a), cirq.X(b)),
611
+ cirq.Moment(phased_xz_gate.on(a), cirq.X(b)),
612
+ cirq.Moment(phased_xz_gate.on(a), cirq.X(b)),
613
+ cirq.Moment(cirq.CZ(a, b)),
614
+ cirq.Moment(cirq.Z(a), cirq.X(b)),
615
+ ),
616
+ schema='XX_PAIR',
617
+ single_qubit_gate_moments_only=False,
618
+ )
619
+
620
+
621
+ def test_insert_new_moment():
622
+ """Test case diagrams.
623
+ Input:
624
+ a: ───H───────H───@───@───────
625
+ │ │
626
+ b: ───H───H───H───X───@^0.5───
627
+
628
+ c: ───H───────────────H───────
629
+ Output:
630
+ a: ───H───X───H───@───Z───@────────────────────────
631
+ │ │
632
+ b: ───H───H───H───X───────@^0.5────────────────────
633
+
634
+ c: ───H───X───X───────X───PhXZ(a=-0.5,x=0.5,z=0)───
635
+ """
636
+ a = cirq.NamedQubit('a')
637
+ b = cirq.NamedQubit('b')
638
+ c = cirq.NamedQubit('c')
639
+ assert_dd(
640
+ input_circuit=cirq.Circuit(
641
+ cirq.Moment(cirq.H(a), cirq.H(b), cirq.H(c)),
642
+ cirq.Moment(cirq.H(b)),
643
+ cirq.Moment(cirq.H(b), cirq.H(a)),
644
+ cirq.Moment(cirq.CNOT(a, b)),
645
+ cirq.Moment(cirq.CZPowGate(exponent=0.5).on(a, b), cirq.H(c)),
646
+ ),
647
+ expected_circuit=cirq.Circuit(
648
+ cirq.Moment(cirq.H(a), cirq.H(b), cirq.H(c)),
649
+ cirq.Moment(cirq.H(b), cirq.X(a), cirq.X(c)),
650
+ cirq.Moment(cirq.H(a), cirq.H(b), cirq.X(c)),
651
+ cirq.Moment(cirq.CNOT(a, b)),
652
+ cirq.Moment(cirq.Z(a), cirq.X(c)),
653
+ cirq.Moment(
654
+ cirq.CZPowGate(exponent=0.5).on(a, b),
655
+ cirq.PhasedXZGate(axis_phase_exponent=-0.5, x_exponent=0.5, z_exponent=0).on(c),
656
+ ),
657
+ ),
658
+ schema="XX_PAIR",
659
+ )
660
+
661
+
582
662
  def test_with_non_clifford_measurements():
583
663
  """Test case diagrams.
584
664
  Input:
@@ -626,3 +706,71 @@ def test_with_non_clifford_measurements():
626
706
  schema="XX_PAIR",
627
707
  single_qubit_gate_moments_only=True,
628
708
  )
709
+
710
+
711
+ def test_cross_clifford_pieces_filling_merge():
712
+ # pylint: disable=line-too-long
713
+ """Test case diagrams.
714
+ Input:
715
+ 0: ─────────────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)───H───
716
+ │ │
717
+ 1: ─────────────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)───H───
718
+
719
+ 2: ───PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────────H───
720
+ │ │ │
721
+ 3: ─────────────────────────────┼───PhXZ(a=0.2,x=0.2,z=0.1)───@───────────────────────────────────────────────────@─────────────────────────────H───
722
+
723
+ 4: ─────────────────────────────┼─────────────────────────────@─────────────────────────────────────────────────────────────────────────────────H───
724
+ │ │
725
+ 5: ───PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)───H───
726
+
727
+ 6: ───────────────────────────────────────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)─────────────────────────────@───PhXZ(a=0.2,x=0.2,z=0.1)───H───
728
+ Output:
729
+
730
+ 0: ─────────────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)─────H────────────────────────
731
+ │ │
732
+ 1: ─────────────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)─────H────────────────────────
733
+
734
+ 2: ───PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@───X───────────────────────────PhXZ(a=0.5,x=0.5,z=-1)───
735
+ │ │ │
736
+ 3: ─────────────────────────────┼───PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────X─────────────────────────@───Y───────────────────────────PhXZ(a=0.5,x=0.5,z=0)────
737
+
738
+ 4: ─────────────────────────────┼─────────────────────────────@─────────────────────────X─────────────────────────────Y───────────────────────────PhXZ(a=0.5,x=0.5,z=0)────
739
+ │ │
740
+ 5: ───PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=-0.8,x=0.2,z=-0.9)───H────────────────────────
741
+
742
+ 6: ───────────────────────────────────────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───X─────────────────────────@───PhXZ(a=0.8,x=0.8,z=0.5)─────H────────────────────────
743
+ """
744
+ # pylint: enable
745
+ qubits = cirq.LineQubit.range(7)
746
+ phased_xz_gate = cirq.PhasedXZGate(axis_phase_exponent=0.2, x_exponent=0.2, z_exponent=0.1)
747
+ assert_dd(
748
+ input_circuit=cirq.Circuit(
749
+ cirq.Moment([phased_xz_gate.on(qubits[i]) for i in [2, 5]]),
750
+ cirq.Moment(cirq.CZ(qubits[2], qubits[5])),
751
+ cirq.Moment([phased_xz_gate.on(qubits[i]) for i in [0, 1, 2, 3, 5]]),
752
+ cirq.Moment(
753
+ [cirq.CZ(qubits[i0], qubits[i1]) for i0, i1 in [(0, 1), (2, 3), (4, 5)]]
754
+ + [phased_xz_gate.on(qubits[6])]
755
+ ),
756
+ cirq.Moment([phased_xz_gate.on(qubits[i]) for i in [0, 1, 2, 5]]),
757
+ cirq.Moment([cirq.CZ(qubits[i0], qubits[i1]) for i0, i1 in [(0, 1), (2, 3), (5, 6)]]),
758
+ cirq.Moment([phased_xz_gate.on(qubits[i]) for i in [0, 1, 5, 6]]),
759
+ cirq.Moment([cirq.H.on(q) for q in qubits]),
760
+ ),
761
+ expected_circuit="""
762
+ 0: ─────────────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)─────H────────────────────────
763
+ │ │
764
+ 1: ─────────────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)─────H────────────────────────
765
+
766
+ 2: ───PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@───X───────────────────────────PhXZ(a=0.5,x=0.5,z=-1)───
767
+ │ │ │
768
+ 3: ─────────────────────────────┼───PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────X─────────────────────────@───Y───────────────────────────PhXZ(a=0.5,x=0.5,z=0)────
769
+
770
+ 4: ─────────────────────────────┼─────────────────────────────@─────────────────────────X─────────────────────────────Y───────────────────────────PhXZ(a=0.5,x=0.5,z=0)────
771
+ │ │
772
+ 5: ───PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=0.2,x=0.2,z=0.1)───@─────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───@───PhXZ(a=-0.8,x=0.2,z=-0.9)───H────────────────────────
773
+
774
+ 6: ───────────────────────────────────────────────────────────PhXZ(a=0.2,x=0.2,z=0.1)───X─────────────────────────@───PhXZ(a=0.8,x=0.8,z=0.5)─────H────────────────────────
775
+ """,
776
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cirq-core
3
- Version: 1.5.0.dev20240920235700
3
+ Version: 1.5.0.dev20240924062245
4
4
  Summary: A framework for creating, editing, and invoking Noisy Intermediate Scale Quantum (NISQ) circuits.
5
5
  Home-page: http://github.com/quantumlib/cirq
6
6
  Author: The Cirq Developers
@@ -4,8 +4,8 @@ cirq/_compat_test.py,sha256=Qq3ZcfgD-Nb81cEppQdJqhAyrVqXKtfXZYGXT0p-Wh0,34718
4
4
  cirq/_doc.py,sha256=yDyWUD_2JDS0gShfGRb-rdqRt9-WeL7DhkqX7np0Nko,2879
5
5
  cirq/_import.py,sha256=p9gMHJscbtDDkfHOaulvd3Aer0pwUF5AXpL89XR8dNw,8402
6
6
  cirq/_import_test.py,sha256=6K_v0riZJXOXUphHNkGA8MY-JcmGlezFaGmvrNhm3OQ,1015
7
- cirq/_version.py,sha256=UU0PwBU9AI9_0YB5TNrjQo0YEbbYebKJ9FlnnNP4aa0,1206
8
- cirq/_version_test.py,sha256=tSVW8IXMr6w7kpNf_xTvF1-c5HgksJ2qx97e1p-EYH0,147
7
+ cirq/_version.py,sha256=R4jW2E0BIRd0k-1pvJuZVGyyJn5zScrlrCYyvAGSRjU,1206
8
+ cirq/_version_test.py,sha256=rw9k2scqHgWFkAJeoIL1fzBrhoKHydEZl3zQHtpljeQ,147
9
9
  cirq/conftest.py,sha256=X7yLFL8GLhg2CjPw0hp5e_dGASfvHx1-QT03aUbhKJw,1168
10
10
  cirq/json_resolver_cache.py,sha256=ytePZtNZgKjOF2NiVpUTuotB-JKZmQNOFIFdvXqsxHw,13271
11
11
  cirq/py.typed,sha256=VFSlmh_lNwnaXzwY-ZuW-C2Ws5PkuDoVgBdNCs0jXJE,63
@@ -1029,8 +1029,8 @@ cirq/transformers/drop_empty_moments.py,sha256=Rtn_BrpwkLXyZBdLzwdnsnEGWTdYuf1xO
1029
1029
  cirq/transformers/drop_empty_moments_test.py,sha256=G8pZmTfi8NG2NpGz_K3LZu5NQoqa-xPMCuZjwEu07xk,1907
1030
1030
  cirq/transformers/drop_negligible_operations.py,sha256=8eyOMy7bra2wJAjORbk6QjwHiLdL5SfwRaz8D2Dazbw,2083
1031
1031
  cirq/transformers/drop_negligible_operations_test.py,sha256=gqL6RoDPm6Zf4RxtprBenFyIsZQPUxmPur9oRl0Yr3U,3823
1032
- cirq/transformers/dynamical_decoupling.py,sha256=OSaJy55nYJmCVgan0VBlxcXeTKPuiJMJDTnnsaKGBFs,12329
1033
- cirq/transformers/dynamical_decoupling_test.py,sha256=kXngZhzV_58cPqpx-zhMmabGzaUKEN_9iS3YV7U7fEE,28410
1032
+ cirq/transformers/dynamical_decoupling.py,sha256=SDDWhy3NZH8MKr6GbGeslkKLa2W_LpxW6obDsubiVQQ,14603
1033
+ cirq/transformers/dynamical_decoupling_test.py,sha256=GlZMhGxYgHJ9nKH0tDSrK3mjzJoxrr4NB3zNngP0YQI,42374
1034
1034
  cirq/transformers/eject_phased_paulis.py,sha256=usuPCxHgZf6Aw6pqIU4vOvaOypH4SiT2lY8VwAnlObs,13975
1035
1035
  cirq/transformers/eject_phased_paulis_test.py,sha256=-mXsfbi3V0ojC_YqoQM5otzdW4kjGusCx6F-kCv8M98,15834
1036
1036
  cirq/transformers/eject_z.py,sha256=d53z1siUVCPrtNbPls6_RSujz6d2gF77_AQAWhnJmVM,5823
@@ -1184,8 +1184,8 @@ cirq/work/sampler.py,sha256=JEAeQQRF3bqlO9AkOf4XbrTATDI5f5JgyM_FAUCNxao,19751
1184
1184
  cirq/work/sampler_test.py,sha256=B2ZsuqGT854gQtBIAh8k0LiG9Vj5wSzcGvkxOUoTcW4,13217
1185
1185
  cirq/work/zeros_sampler.py,sha256=x1C7cup66a43n-3tm8QjhiqJa07qcJW10FxNp9jJ59Q,2356
1186
1186
  cirq/work/zeros_sampler_test.py,sha256=JIkpBBFPJe5Ba4142vzogyWyboG1Q1ZAm0UVGgOoZn8,3279
1187
- cirq_core-1.5.0.dev20240920235700.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
1188
- cirq_core-1.5.0.dev20240920235700.dist-info/METADATA,sha256=o7bkLtS0hBkqaYk9FKMrbz081EPlQsJYw46D9Skv7h0,1992
1189
- cirq_core-1.5.0.dev20240920235700.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
1190
- cirq_core-1.5.0.dev20240920235700.dist-info/top_level.txt,sha256=Sz9iOxHU0IEMLSFGwiwOCaN2e9K-jFbBbtpPN1hB73g,5
1191
- cirq_core-1.5.0.dev20240920235700.dist-info/RECORD,,
1187
+ cirq_core-1.5.0.dev20240924062245.dist-info/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
1188
+ cirq_core-1.5.0.dev20240924062245.dist-info/METADATA,sha256=MzdTLX6dns1jSsOV_6fvhP75oDNKg_ey9xreAqX9d8o,1992
1189
+ cirq_core-1.5.0.dev20240924062245.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
1190
+ cirq_core-1.5.0.dev20240924062245.dist-info/top_level.txt,sha256=Sz9iOxHU0IEMLSFGwiwOCaN2e9K-jFbBbtpPN1hB73g,5
1191
+ cirq_core-1.5.0.dev20240924062245.dist-info/RECORD,,