cirq-core 1.3.0.dev20231201164435__py3-none-any.whl → 1.4.0__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.

Files changed (157) hide show
  1. cirq/__init__.py +4 -0
  2. cirq/_compat.py +9 -11
  3. cirq/_compat_test.py +45 -56
  4. cirq/_version.py +31 -1
  5. cirq/_version_test.py +1 -1
  6. cirq/circuits/circuit.py +13 -8
  7. cirq/circuits/circuit_operation.py +2 -1
  8. cirq/circuits/circuit_test.py +2 -2
  9. cirq/circuits/frozen_circuit.py +3 -2
  10. cirq/circuits/moment.py +12 -10
  11. cirq/circuits/qasm_output.py +5 -1
  12. cirq/circuits/qasm_output_test.py +25 -10
  13. cirq/contrib/qcircuit/qcircuit_diagram_info.py +9 -7
  14. cirq/contrib/quimb/mps_simulator_test.py +1 -1
  15. cirq/contrib/quimb/state_vector.py +9 -2
  16. cirq/contrib/svg/svg.py +2 -1
  17. cirq/contrib/svg/svg_test.py +1 -0
  18. cirq/devices/grid_qubit.py +85 -32
  19. cirq/devices/grid_qubit_test.py +22 -4
  20. cirq/devices/line_qubit.py +74 -26
  21. cirq/devices/line_qubit_test.py +19 -0
  22. cirq/devices/noise_utils.py +33 -31
  23. cirq/devices/noise_utils_test.py +1 -84
  24. cirq/devices/superconducting_qubits_noise_properties.py +7 -6
  25. cirq/experiments/__init__.py +8 -0
  26. cirq/experiments/qubit_characterizations.py +288 -44
  27. cirq/experiments/qubit_characterizations_test.py +61 -7
  28. cirq/experiments/random_quantum_circuit_generation.py +1 -1
  29. cirq/experiments/single_qubit_readout_calibration.py +132 -6
  30. cirq/experiments/single_qubit_readout_calibration_test.py +3 -1
  31. cirq/experiments/t1_decay_experiment.py +14 -7
  32. cirq/experiments/t1_decay_experiment_test.py +14 -26
  33. cirq/experiments/two_qubit_xeb.py +483 -0
  34. cirq/experiments/two_qubit_xeb_test.py +304 -0
  35. cirq/json_resolver_cache.py +2 -0
  36. cirq/linalg/decompositions.py +11 -13
  37. cirq/linalg/decompositions_test.py +1 -3
  38. cirq/linalg/diagonalize.py +5 -4
  39. cirq/linalg/predicates.py +8 -6
  40. cirq/linalg/transformations.py +2 -1
  41. cirq/linalg/transformations_test.py +1 -1
  42. cirq/ops/__init__.py +2 -0
  43. cirq/ops/clifford_gate.py +59 -16
  44. cirq/ops/common_gates_test.py +1 -2
  45. cirq/ops/control_values.py +4 -3
  46. cirq/ops/controlled_gate_test.py +1 -3
  47. cirq/ops/gate_operation.py +10 -1
  48. cirq/ops/named_qubit.py +74 -28
  49. cirq/ops/named_qubit_test.py +19 -0
  50. cirq/ops/parity_gates.py +5 -0
  51. cirq/ops/parity_gates_test.py +2 -10
  52. cirq/ops/pauli_gates.py +5 -2
  53. cirq/ops/pauli_string.py +2 -2
  54. cirq/ops/permutation_gate.py +16 -18
  55. cirq/ops/phased_iswap_gate_test.py +1 -3
  56. cirq/ops/phased_x_gate.py +1 -1
  57. cirq/ops/phased_x_z_gate.py +17 -1
  58. cirq/ops/phased_x_z_gate_test.py +24 -0
  59. cirq/ops/qid_util.py +4 -8
  60. cirq/ops/qubit_manager.py +7 -4
  61. cirq/ops/qubit_manager_test.py +20 -0
  62. cirq/ops/raw_types.py +5 -2
  63. cirq/ops/raw_types_test.py +14 -15
  64. cirq/ops/uniform_superposition_gate.py +123 -0
  65. cirq/ops/uniform_superposition_gate_test.py +94 -0
  66. cirq/protocols/approximate_equality_protocol_test.py +2 -2
  67. cirq/protocols/circuit_diagram_info_protocol.py +6 -4
  68. cirq/protocols/commutes_protocol.py +2 -4
  69. cirq/protocols/decompose_protocol.py +7 -12
  70. cirq/protocols/decompose_protocol_test.py +7 -3
  71. cirq/protocols/has_stabilizer_effect_protocol.py +1 -5
  72. cirq/protocols/has_stabilizer_effect_protocol_test.py +13 -4
  73. cirq/protocols/json_serialization.py +51 -181
  74. cirq/protocols/json_serialization_test.py +13 -47
  75. cirq/protocols/json_test_data/CircuitOperation.json +131 -148
  76. cirq/protocols/json_test_data/CircuitOperation.json_inward +55 -0
  77. cirq/protocols/json_test_data/CircuitOperation.repr_inward +6 -0
  78. cirq/protocols/json_test_data/FrozenCircuit.json +196 -210
  79. cirq/protocols/json_test_data/FrozenCircuit.json_inward +35 -0
  80. cirq/protocols/json_test_data/FrozenCircuit.repr_inward +4 -0
  81. cirq/protocols/json_test_data/UniformSuperpositionGate.json +5 -0
  82. cirq/protocols/json_test_data/UniformSuperpositionGate.repr +1 -0
  83. cirq/protocols/json_test_data/cirq.MSGate.json +4 -0
  84. cirq/protocols/json_test_data/cirq.MSGate.repr +1 -0
  85. cirq/protocols/json_test_data/spec.py +2 -0
  86. cirq/protocols/pow_protocol_test.py +1 -3
  87. cirq/protocols/resolve_parameters.py +4 -2
  88. cirq/qis/__init__.py +10 -0
  89. cirq/qis/clifford_tableau.py +8 -2
  90. cirq/qis/noise_utils.py +123 -0
  91. cirq/qis/noise_utils_test.py +97 -0
  92. cirq/sim/classical_simulator.py +227 -87
  93. cirq/sim/classical_simulator_test.py +135 -0
  94. cirq/sim/clifford/clifford_simulator_test.py +4 -2
  95. cirq/sim/mux.py +5 -3
  96. cirq/sim/simulation_product_state.py +15 -10
  97. cirq/sim/simulation_state.py +1 -1
  98. cirq/sim/simulation_state_test.py +2 -2
  99. cirq/sim/simulator_base.py +3 -3
  100. cirq/sim/state_vector_simulation_state.py +4 -4
  101. cirq/sim/state_vector_simulator.py +17 -2
  102. cirq/study/__init__.py +1 -0
  103. cirq/study/result.py +14 -0
  104. cirq/study/result_test.py +6 -0
  105. cirq/study/sweeps.py +4 -2
  106. cirq/study/sweeps_test.py +8 -0
  107. cirq/testing/__init__.py +6 -1
  108. cirq/testing/_compat_test_data/__init__.py +3 -3
  109. cirq/testing/_compat_test_data/module_a/__init__.py +2 -2
  110. cirq/testing/circuit_compare.py +1 -1
  111. cirq/testing/consistent_qasm.py +6 -0
  112. cirq/testing/gate_features.py +10 -0
  113. cirq/testing/lin_alg_utils.py +5 -3
  114. cirq/transformers/__init__.py +15 -0
  115. cirq/transformers/analytical_decompositions/controlled_gate_decomposition.py +3 -1
  116. cirq/transformers/analytical_decompositions/two_qubit_to_cz.py +24 -0
  117. cirq/transformers/analytical_decompositions/two_qubit_to_cz_test.py +17 -0
  118. cirq/transformers/dynamical_decoupling.py +122 -0
  119. cirq/transformers/dynamical_decoupling_test.py +123 -0
  120. cirq/transformers/gauge_compiling/__init__.py +26 -0
  121. cirq/transformers/gauge_compiling/cz_gauge.py +46 -0
  122. cirq/transformers/gauge_compiling/cz_gauge_test.py +23 -0
  123. cirq/transformers/gauge_compiling/gauge_compiling.py +214 -0
  124. cirq/transformers/gauge_compiling/gauge_compiling_test.py +41 -0
  125. cirq/transformers/gauge_compiling/gauge_compiling_test_utils.py +83 -0
  126. cirq/transformers/gauge_compiling/gauge_compiling_test_utils_test.py +52 -0
  127. cirq/transformers/gauge_compiling/iswap_gauge.py +105 -0
  128. cirq/transformers/gauge_compiling/iswap_gauge_test.py +23 -0
  129. cirq/transformers/gauge_compiling/spin_inversion_gauge.py +33 -0
  130. cirq/transformers/gauge_compiling/spin_inversion_gauge_test.py +37 -0
  131. cirq/transformers/gauge_compiling/sqrt_cz_gauge.py +64 -0
  132. cirq/transformers/gauge_compiling/sqrt_cz_gauge_test.py +27 -0
  133. cirq/transformers/gauge_compiling/sqrt_iswap_gauge.py +94 -0
  134. cirq/transformers/gauge_compiling/sqrt_iswap_gauge_test.py +22 -0
  135. cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation.py +1 -0
  136. cirq/transformers/merge_k_qubit_gates_test.py +23 -23
  137. cirq/transformers/merge_single_qubit_gates_test.py +14 -14
  138. cirq/transformers/optimize_for_target_gateset.py +39 -17
  139. cirq/transformers/optimize_for_target_gateset_test.py +189 -39
  140. cirq/transformers/qubit_management_transformers.py +1 -1
  141. cirq/transformers/routing/visualize_routed_circuit_test.py +17 -17
  142. cirq/transformers/stratify_test.py +13 -13
  143. cirq/transformers/target_gatesets/compilation_target_gateset.py +26 -2
  144. cirq/transformers/target_gatesets/compilation_target_gateset_test.py +16 -16
  145. cirq/transformers/target_gatesets/cz_gateset.py +4 -0
  146. cirq/transformers/transformer_api.py +1 -2
  147. cirq/transformers/transformer_primitives.py +15 -14
  148. cirq/transformers/transformer_primitives_test.py +99 -72
  149. cirq/value/classical_data.py +6 -6
  150. cirq/value/value_equality_attr.py +4 -0
  151. cirq/work/sampler.py +3 -4
  152. cirq/work/sampler_test.py +25 -0
  153. {cirq_core-1.3.0.dev20231201164435.dist-info → cirq_core-1.4.0.dist-info}/METADATA +10 -19
  154. {cirq_core-1.3.0.dev20231201164435.dist-info → cirq_core-1.4.0.dist-info}/RECORD +157 -130
  155. {cirq_core-1.3.0.dev20231201164435.dist-info → cirq_core-1.4.0.dist-info}/WHEEL +1 -1
  156. {cirq_core-1.3.0.dev20231201164435.dist-info → cirq_core-1.4.0.dist-info}/LICENSE +0 -0
  157. {cirq_core-1.3.0.dev20231201164435.dist-info → cirq_core-1.4.0.dist-info}/top_level.txt +0 -0
@@ -14,6 +14,7 @@
14
14
 
15
15
  import abc
16
16
  import functools
17
+ import weakref
17
18
  from typing import Any, Dict, Iterable, List, Optional, Tuple, Set, TYPE_CHECKING, Union
18
19
  from typing_extensions import Self
19
20
 
@@ -32,43 +33,66 @@ class _BaseGridQid(ops.Qid):
32
33
  _row: int
33
34
  _col: int
34
35
  _dimension: int
36
+ _comp_key: Optional[Tuple[int, int]] = None
35
37
  _hash: Optional[int] = None
36
38
 
37
- def __getstate__(self):
38
- # Don't save hash when pickling; see #3777.
39
- state = self.__dict__
40
- if "_hash" in state:
41
- state = state.copy()
42
- del state["_hash"]
43
- return state
44
-
45
39
  def __hash__(self) -> int:
46
40
  if self._hash is None:
47
41
  self._hash = hash((self._row, self._col, self._dimension))
48
42
  return self._hash
49
43
 
50
- def __eq__(self, other):
44
+ def __eq__(self, other) -> bool:
51
45
  # Explicitly implemented for performance (vs delegating to Qid).
52
46
  if isinstance(other, _BaseGridQid):
53
- return (
47
+ return self is other or (
54
48
  self._row == other._row
55
49
  and self._col == other._col
56
50
  and self._dimension == other._dimension
57
51
  )
58
52
  return NotImplemented
59
53
 
60
- def __ne__(self, other):
54
+ def __ne__(self, other) -> bool:
61
55
  # Explicitly implemented for performance (vs delegating to Qid).
62
56
  if isinstance(other, _BaseGridQid):
63
- return (
57
+ return self is not other and (
64
58
  self._row != other._row
65
59
  or self._col != other._col
66
60
  or self._dimension != other._dimension
67
61
  )
68
62
  return NotImplemented
69
63
 
64
+ def __lt__(self, other) -> bool:
65
+ # Explicitly implemented for performance (vs delegating to Qid).
66
+ if isinstance(other, _BaseGridQid):
67
+ k0, k1 = self._comparison_key(), other._comparison_key()
68
+ return k0 < k1 or (k0 == k1 and self._dimension < other._dimension)
69
+ return super().__lt__(other)
70
+
71
+ def __le__(self, other) -> bool:
72
+ # Explicitly implemented for performance (vs delegating to Qid).
73
+ if isinstance(other, _BaseGridQid):
74
+ k0, k1 = self._comparison_key(), other._comparison_key()
75
+ return k0 < k1 or (k0 == k1 and self._dimension <= other._dimension)
76
+ return super().__le__(other)
77
+
78
+ def __ge__(self, other) -> bool:
79
+ # Explicitly implemented for performance (vs delegating to Qid).
80
+ if isinstance(other, _BaseGridQid):
81
+ k0, k1 = self._comparison_key(), other._comparison_key()
82
+ return k0 > k1 or (k0 == k1 and self._dimension >= other._dimension)
83
+ return super().__ge__(other)
84
+
85
+ def __gt__(self, other) -> bool:
86
+ # Explicitly implemented for performance (vs delegating to Qid).
87
+ if isinstance(other, _BaseGridQid):
88
+ k0, k1 = self._comparison_key(), other._comparison_key()
89
+ return k0 > k1 or (k0 == k1 and self._dimension > other._dimension)
90
+ return super().__gt__(other)
91
+
70
92
  def _comparison_key(self):
71
- return self._row, self._col
93
+ if self._comp_key is None:
94
+ self._comp_key = self._row, self._col
95
+ return self._comp_key
72
96
 
73
97
  @property
74
98
  def row(self) -> int:
@@ -178,8 +202,12 @@ class GridQid(_BaseGridQid):
178
202
  cirq.GridQid(5, 4, dimension=2)
179
203
  """
180
204
 
181
- def __init__(self, row: int, col: int, *, dimension: int) -> None:
182
- """Initializes a grid qid at the given row, col coordinate
205
+ # Cache of existing GridQid instances, returned by __new__ if available.
206
+ # Holds weak references so instances can still be garbage collected.
207
+ _cache = weakref.WeakValueDictionary[Tuple[int, int, int], 'cirq.GridQid']()
208
+
209
+ def __new__(cls, row: int, col: int, *, dimension: int) -> 'cirq.GridQid':
210
+ """Creates a grid qid at the given row, col coordinate
183
211
 
184
212
  Args:
185
213
  row: the row coordinate
@@ -187,13 +215,23 @@ class GridQid(_BaseGridQid):
187
215
  dimension: The dimension of the qid's Hilbert space, i.e.
188
216
  the number of quantum levels.
189
217
  """
190
- self.validate_dimension(dimension)
191
- self._row = row
192
- self._col = col
193
- self._dimension = dimension
218
+ key = (row, col, dimension)
219
+ inst = cls._cache.get(key)
220
+ if inst is None:
221
+ cls.validate_dimension(dimension)
222
+ inst = super().__new__(cls)
223
+ inst._row = row
224
+ inst._col = col
225
+ inst._dimension = dimension
226
+ cls._cache[key] = inst
227
+ return inst
228
+
229
+ def __getnewargs_ex__(self):
230
+ """Returns a tuple of (args, kwargs) to pass to __new__ when unpickling."""
231
+ return (self._row, self._col), {"dimension": self._dimension}
194
232
 
195
233
  def _with_row_col(self, row: int, col: int) -> 'GridQid':
196
- return GridQid(row, col, dimension=self.dimension)
234
+ return GridQid(row, col, dimension=self._dimension)
197
235
 
198
236
  @staticmethod
199
237
  def square(diameter: int, top: int = 0, left: int = 0, *, dimension: int) -> List['GridQid']:
@@ -290,16 +328,16 @@ class GridQid(_BaseGridQid):
290
328
  return [GridQid(*c, dimension=dimension) for c in coords]
291
329
 
292
330
  def __repr__(self) -> str:
293
- return f"cirq.GridQid({self._row}, {self._col}, dimension={self.dimension})"
331
+ return f"cirq.GridQid({self._row}, {self._col}, dimension={self._dimension})"
294
332
 
295
333
  def __str__(self) -> str:
296
- return f"q({self._row}, {self._col}) (d={self.dimension})"
334
+ return f"q({self._row}, {self._col}) (d={self._dimension})"
297
335
 
298
336
  def _circuit_diagram_info_(
299
337
  self, args: 'cirq.CircuitDiagramInfoArgs'
300
338
  ) -> 'cirq.CircuitDiagramInfo':
301
339
  return protocols.CircuitDiagramInfo(
302
- wire_symbols=(f"({self._row}, {self._col}) (d={self.dimension})",)
340
+ wire_symbols=(f"({self._row}, {self._col}) (d={self._dimension})",)
303
341
  )
304
342
 
305
343
  def _json_dict_(self) -> Dict[str, Any]:
@@ -325,17 +363,32 @@ class GridQubit(_BaseGridQid):
325
363
 
326
364
  _dimension = 2
327
365
 
328
- def __init__(self, row: int, col: int) -> None:
329
- self._row = row
330
- self._col = col
366
+ # Cache of existing GridQubit instances, returned by __new__ if available.
367
+ # Holds weak references so instances can still be garbage collected.
368
+ _cache = weakref.WeakValueDictionary[Tuple[int, int], 'cirq.GridQubit']()
331
369
 
332
- def _with_row_col(self, row: int, col: int):
333
- return GridQubit(row, col)
370
+ def __new__(cls, row: int, col: int) -> 'cirq.GridQubit':
371
+ """Creates a grid qubit at the given row, col coordinate
334
372
 
335
- def _cmp_tuple(self):
336
- cls = GridQid if type(self) is GridQubit else type(self)
337
- # Must be same as Qid._cmp_tuple but with cls in place of type(self).
338
- return (cls.__name__, repr(cls), self._comparison_key(), self.dimension)
373
+ Args:
374
+ row: the row coordinate
375
+ col: the column coordinate
376
+ """
377
+ key = (row, col)
378
+ inst = cls._cache.get(key)
379
+ if inst is None:
380
+ inst = super().__new__(cls)
381
+ inst._row = row
382
+ inst._col = col
383
+ cls._cache[key] = inst
384
+ return inst
385
+
386
+ def __getnewargs__(self):
387
+ """Returns a tuple of args to pass to __new__ when unpickling."""
388
+ return (self._row, self._col)
389
+
390
+ def _with_row_col(self, row: int, col: int) -> 'GridQubit':
391
+ return GridQubit(row, col)
339
392
 
340
393
  @staticmethod
341
394
  def square(diameter: int, top: int = 0, left: int = 0) -> List['GridQubit']:
@@ -40,11 +40,29 @@ def test_eq():
40
40
  eq.make_equality_group(lambda: cirq.GridQid(0, 0, dimension=3))
41
41
 
42
42
 
43
- def test_pickled_hash():
44
- q = cirq.GridQubit(3, 4)
45
- q_bad = cirq.GridQubit(3, 4)
43
+ def test_grid_qubit_pickled_hash():
44
+ # Use a large number that is unlikely to be used by any other tests.
45
+ row, col = 123456789, 2345678910
46
+ q_bad = cirq.GridQubit(row, col)
47
+ cirq.GridQubit._cache.pop((row, col))
48
+ q = cirq.GridQubit(row, col)
49
+ _test_qid_pickled_hash(q, q_bad)
50
+
51
+
52
+ def test_grid_qid_pickled_hash():
53
+ # Use a large number that is unlikely to be used by any other tests.
54
+ row, col = 123456789, 2345678910
55
+ q_bad = cirq.GridQid(row, col, dimension=3)
56
+ cirq.GridQid._cache.pop((row, col, 3))
57
+ q = cirq.GridQid(row, col, dimension=3)
58
+ _test_qid_pickled_hash(q, q_bad)
59
+
60
+
61
+ def _test_qid_pickled_hash(q: 'cirq.Qid', q_bad: 'cirq.Qid') -> None:
62
+ """Test that hashes are not pickled with Qid instances."""
63
+ assert q_bad is not q
46
64
  _ = hash(q_bad) # compute hash to ensure it is cached.
47
- q_bad._hash = q_bad._hash + 1
65
+ q_bad._hash = q_bad._hash + 1 # type: ignore[attr-defined]
48
66
  assert q_bad == q
49
67
  assert hash(q_bad) != hash(q)
50
68
  data = pickle.dumps(q_bad)
@@ -14,7 +14,8 @@
14
14
 
15
15
  import abc
16
16
  import functools
17
- from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, TYPE_CHECKING, Union
17
+ import weakref
18
+ from typing import Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple, TYPE_CHECKING, Union
18
19
  from typing_extensions import Self
19
20
 
20
21
  from cirq import ops, protocols
@@ -31,31 +32,57 @@ class _BaseLineQid(ops.Qid):
31
32
  _dimension: int
32
33
  _hash: Optional[int] = None
33
34
 
34
- def __getstate__(self):
35
- # Don't save hash when pickling; see #3777.
36
- state = self.__dict__
37
- if "_hash" in state:
38
- state = state.copy()
39
- del state["_hash"]
40
- return state
41
-
42
35
  def __hash__(self) -> int:
43
36
  if self._hash is None:
44
37
  self._hash = hash((self._x, self._dimension))
45
38
  return self._hash
46
39
 
47
- def __eq__(self, other):
40
+ def __eq__(self, other) -> bool:
48
41
  # Explicitly implemented for performance (vs delegating to Qid).
49
42
  if isinstance(other, _BaseLineQid):
50
- return self._x == other._x and self._dimension == other._dimension
43
+ return self is other or (self._x == other._x and self._dimension == other._dimension)
51
44
  return NotImplemented
52
45
 
53
- def __ne__(self, other):
46
+ def __ne__(self, other) -> bool:
54
47
  # Explicitly implemented for performance (vs delegating to Qid).
55
48
  if isinstance(other, _BaseLineQid):
56
- return self._x != other._x or self._dimension != other._dimension
49
+ return self is not other and (
50
+ self._x != other._x or self._dimension != other._dimension
51
+ )
57
52
  return NotImplemented
58
53
 
54
+ def __lt__(self, other) -> bool:
55
+ # Explicitly implemented for performance (vs delegating to Qid).
56
+ if isinstance(other, _BaseLineQid):
57
+ return self._x < other._x or (
58
+ self._x == other._x and self._dimension < other._dimension
59
+ )
60
+ return super().__lt__(other)
61
+
62
+ def __le__(self, other) -> bool:
63
+ # Explicitly implemented for performance (vs delegating to Qid).
64
+ if isinstance(other, _BaseLineQid):
65
+ return self._x < other._x or (
66
+ self._x == other._x and self._dimension <= other._dimension
67
+ )
68
+ return super().__le__(other)
69
+
70
+ def __ge__(self, other) -> bool:
71
+ # Explicitly implemented for performance (vs delegating to Qid).
72
+ if isinstance(other, _BaseLineQid):
73
+ return self._x > other._x or (
74
+ self._x == other._x and self._dimension >= other._dimension
75
+ )
76
+ return super().__ge__(other)
77
+
78
+ def __gt__(self, other) -> bool:
79
+ # Explicitly implemented for performance (vs delegating to Qid).
80
+ if isinstance(other, _BaseLineQid):
81
+ return self._x > other._x or (
82
+ self._x == other._x and self._dimension > other._dimension
83
+ )
84
+ return super().__gt__(other)
85
+
59
86
  def _comparison_key(self):
60
87
  return self._x
61
88
 
@@ -154,7 +181,11 @@ class LineQid(_BaseLineQid):
154
181
 
155
182
  """
156
183
 
157
- def __init__(self, x: int, dimension: int) -> None:
184
+ # Cache of existing LineQid instances, returned by __new__ if available.
185
+ # Holds weak references so instances can still be garbage collected.
186
+ _cache = weakref.WeakValueDictionary[Tuple[int, int], 'cirq.LineQid']()
187
+
188
+ def __new__(cls, x: int, dimension: int) -> 'cirq.LineQid':
158
189
  """Initializes a line qid at the given x coordinate.
159
190
 
160
191
  Args:
@@ -162,9 +193,19 @@ class LineQid(_BaseLineQid):
162
193
  dimension: The dimension of the qid's Hilbert space, i.e.
163
194
  the number of quantum levels.
164
195
  """
165
- self.validate_dimension(dimension)
166
- self._x = x
167
- self._dimension = dimension
196
+ key = (x, dimension)
197
+ inst = cls._cache.get(key)
198
+ if inst is None:
199
+ cls.validate_dimension(dimension)
200
+ inst = super().__new__(cls)
201
+ inst._x = x
202
+ inst._dimension = dimension
203
+ cls._cache[key] = inst
204
+ return inst
205
+
206
+ def __getnewargs__(self):
207
+ """Returns a tuple of args to pass to __new__ when unpickling."""
208
+ return (self._x, self._dimension)
168
209
 
169
210
  def _with_x(self, x: int) -> 'LineQid':
170
211
  return LineQid(x, dimension=self._dimension)
@@ -246,23 +287,30 @@ class LineQubit(_BaseLineQid):
246
287
 
247
288
  _dimension = 2
248
289
 
249
- def __init__(self, x: int) -> None:
250
- """Initializes a line qubit at the given x coordinate.
290
+ # Cache of existing LineQubit instances, returned by __new__ if available.
291
+ # Holds weak references so instances can still be garbage collected.
292
+ _cache = weakref.WeakValueDictionary[int, 'cirq.LineQubit']()
293
+
294
+ def __new__(cls, x: int) -> 'cirq.LineQubit':
295
+ """Initializes a line qid at the given x coordinate.
251
296
 
252
297
  Args:
253
298
  x: The x coordinate.
254
299
  """
255
- self._x = x
300
+ inst = cls._cache.get(x)
301
+ if inst is None:
302
+ inst = super().__new__(cls)
303
+ inst._x = x
304
+ cls._cache[x] = inst
305
+ return inst
306
+
307
+ def __getnewargs__(self):
308
+ """Returns a tuple of args to pass to __new__ when unpickling."""
309
+ return (self._x,)
256
310
 
257
311
  def _with_x(self, x: int) -> 'LineQubit':
258
312
  return LineQubit(x)
259
313
 
260
- def _cmp_tuple(self):
261
- cls = LineQid if type(self) is LineQubit else type(self)
262
- # Must be the same as Qid._cmp_tuple but with cls in place of
263
- # type(self).
264
- return (cls.__name__, repr(cls), self._comparison_key(), self._dimension)
265
-
266
314
  @staticmethod
267
315
  def range(*range_args) -> List['LineQubit']:
268
316
  """Returns a range of line qubits.
@@ -15,6 +15,7 @@
15
15
  import pytest
16
16
 
17
17
  import cirq
18
+ from cirq.devices.grid_qubit_test import _test_qid_pickled_hash
18
19
 
19
20
 
20
21
  def test_init():
@@ -67,6 +68,24 @@ def test_cmp_failure():
67
68
  _ = cirq.LineQid(1, 3) < 0
68
69
 
69
70
 
71
+ def test_line_qubit_pickled_hash():
72
+ # Use a large number that is unlikely to be used by any other tests.
73
+ x = 1234567891011
74
+ q_bad = cirq.LineQubit(x)
75
+ cirq.LineQubit._cache.pop(x)
76
+ q = cirq.LineQubit(x)
77
+ _test_qid_pickled_hash(q, q_bad)
78
+
79
+
80
+ def test_line_qid_pickled_hash():
81
+ # Use a large number that is unlikely to be used by any other tests.
82
+ x = 1234567891011
83
+ q_bad = cirq.LineQid(x, dimension=3)
84
+ cirq.LineQid._cache.pop((x, 3))
85
+ q = cirq.LineQid(x, dimension=3)
86
+ _test_qid_pickled_hash(q, q_bad)
87
+
88
+
70
89
  def test_is_adjacent():
71
90
  assert cirq.LineQubit(1).is_adjacent(cirq.LineQubit(2))
72
91
  assert cirq.LineQubit(1).is_adjacent(cirq.LineQubit(0))
@@ -13,10 +13,9 @@
13
13
  # limitations under the License.
14
14
 
15
15
  from typing import TYPE_CHECKING, Any, Dict, Tuple, Type, Union
16
- import numpy as np
17
16
 
18
- from cirq import ops, protocols, value
19
- from cirq._compat import proper_repr
17
+ from cirq import ops, protocols, value, qis
18
+ from cirq._compat import proper_repr, deprecated
20
19
 
21
20
  if TYPE_CHECKING:
22
21
  import cirq
@@ -97,8 +96,10 @@ class OpIdentifier:
97
96
  return cls(gate_type, *qubits)
98
97
 
99
98
 
100
- # TODO: expose all from top-level cirq?
101
- def decay_constant_to_xeb_fidelity(decay_constant: float, num_qubits: int = 2) -> float:
99
+ @deprecated(deadline='v2.0', fix='use cirq.qis.decay_constant_to_xeb_fidelity')
100
+ def decay_constant_to_xeb_fidelity(
101
+ decay_constant: float, num_qubits: int = 2
102
+ ) -> float: # pragma: no cover
102
103
  """Calculates the XEB fidelity from the depolarization decay constant.
103
104
 
104
105
  Args:
@@ -108,11 +109,13 @@ def decay_constant_to_xeb_fidelity(decay_constant: float, num_qubits: int = 2) -
108
109
  Returns:
109
110
  Calculated XEB fidelity.
110
111
  """
111
- N = 2**num_qubits
112
- return 1 - ((1 - decay_constant) * (1 - 1 / N))
112
+ return qis.decay_constant_to_xeb_fidelity(decay_constant, num_qubits)
113
113
 
114
114
 
115
- def decay_constant_to_pauli_error(decay_constant: float, num_qubits: int = 1) -> float:
115
+ @deprecated(deadline='v2.0', fix='use cirq.qis.decay_constant_to_pauli_error')
116
+ def decay_constant_to_pauli_error(
117
+ decay_constant: float, num_qubits: int = 1
118
+ ) -> float: # pragma: no cover
116
119
  """Calculates pauli error from the depolarization decay constant.
117
120
 
118
121
  Args:
@@ -122,11 +125,13 @@ def decay_constant_to_pauli_error(decay_constant: float, num_qubits: int = 1) ->
122
125
  Returns:
123
126
  Calculated Pauli error.
124
127
  """
125
- N = 2**num_qubits
126
- return (1 - decay_constant) * (1 - 1 / N / N)
128
+ return qis.decay_constant_to_pauli_error(decay_constant, num_qubits)
127
129
 
128
130
 
129
- def pauli_error_to_decay_constant(pauli_error: float, num_qubits: int = 1) -> float:
131
+ @deprecated(deadline='v2.0', fix='use cirq.qis.pauli_error_to_decay_constant')
132
+ def pauli_error_to_decay_constant(
133
+ pauli_error: float, num_qubits: int = 1
134
+ ) -> float: # pragma: no cover
130
135
  """Calculates depolarization decay constant from pauli error.
131
136
 
132
137
  Args:
@@ -136,11 +141,13 @@ def pauli_error_to_decay_constant(pauli_error: float, num_qubits: int = 1) -> fl
136
141
  Returns:
137
142
  Calculated depolarization decay constant.
138
143
  """
139
- N = 2**num_qubits
140
- return 1 - (pauli_error / (1 - 1 / N / N))
144
+ return qis.pauli_error_to_decay_constant(pauli_error, num_qubits)
141
145
 
142
146
 
143
- def xeb_fidelity_to_decay_constant(xeb_fidelity: float, num_qubits: int = 2) -> float:
147
+ @deprecated(deadline='v2.0', fix='use cirq.qis.xeb_fidelity_to_decay_constant')
148
+ def xeb_fidelity_to_decay_constant(
149
+ xeb_fidelity: float, num_qubits: int = 2
150
+ ) -> float: # pragma: no cover
144
151
  """Calculates the depolarization decay constant from XEB fidelity.
145
152
 
146
153
  Args:
@@ -150,11 +157,11 @@ def xeb_fidelity_to_decay_constant(xeb_fidelity: float, num_qubits: int = 2) ->
150
157
  Returns:
151
158
  Calculated depolarization decay constant.
152
159
  """
153
- N = 2**num_qubits
154
- return 1 - (1 - xeb_fidelity) / (1 - 1 / N)
160
+ return qis.xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits)
155
161
 
156
162
 
157
- def pauli_error_from_t1(t_ns: float, t1_ns: float) -> float:
163
+ @deprecated(deadline='v2.0', fix='use cirq.qis.pauli_error_from_t1')
164
+ def pauli_error_from_t1(t_ns: float, t1_ns: float) -> float: # pragma: no cover
158
165
  """Calculates the pauli error from T1 decay constant.
159
166
 
160
167
  This computes error for a specific duration, `t`.
@@ -166,11 +173,11 @@ def pauli_error_from_t1(t_ns: float, t1_ns: float) -> float:
166
173
  Returns:
167
174
  Calculated Pauli error resulting from T1 decay.
168
175
  """
169
- t2 = 2 * t1_ns
170
- return (1 - np.exp(-t_ns / t2)) / 2 + (1 - np.exp(-t_ns / t1_ns)) / 4
176
+ return qis.pauli_error_from_t1(t_ns, t1_ns)
171
177
 
172
178
 
173
- def average_error(decay_constant: float, num_qubits: int = 1) -> float:
179
+ @deprecated(deadline='v2.0', fix='use cirq.qis.average_error')
180
+ def average_error(decay_constant: float, num_qubits: int = 1) -> float: # pragma: no cover
174
181
  """Calculates the average error from the depolarization decay constant.
175
182
 
176
183
  Args:
@@ -180,11 +187,13 @@ def average_error(decay_constant: float, num_qubits: int = 1) -> float:
180
187
  Returns:
181
188
  Calculated average error.
182
189
  """
183
- N = 2**num_qubits
184
- return (1 - decay_constant) * (1 - 1 / N)
190
+ return qis.average_error(decay_constant, num_qubits)
185
191
 
186
192
 
187
- def decoherence_pauli_error(t1_ns: float, tphi_ns: float, gate_time_ns: float) -> float:
193
+ @deprecated(deadline='v2.0', fix='use cirq.qis.decoherence_pauli_error')
194
+ def decoherence_pauli_error(
195
+ t1_ns: float, tphi_ns: float, gate_time_ns: float
196
+ ) -> float: # pragma: no cover
188
197
  """The component of Pauli error caused by decoherence on a single qubit.
189
198
 
190
199
  Args:
@@ -195,11 +204,4 @@ def decoherence_pauli_error(t1_ns: float, tphi_ns: float, gate_time_ns: float) -
195
204
  Returns:
196
205
  Calculated Pauli error resulting from decoherence.
197
206
  """
198
- gamma_2 = (1 / (2 * t1_ns)) + 1 / tphi_ns
199
-
200
- exp1 = np.exp(-gate_time_ns / t1_ns)
201
- exp2 = np.exp(-gate_time_ns * gamma_2)
202
- px = 0.25 * (1 - exp1)
203
- py = px
204
- pz = 0.5 * (1 - exp2) - px
205
- return px + py + pz
207
+ return qis.decoherence_pauli_error(t1_ns, tphi_ns, gate_time_ns)
@@ -12,20 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- import numpy as np
16
- import pytest
17
-
18
15
  import cirq
19
- from cirq.devices.noise_utils import (
20
- OpIdentifier,
21
- decay_constant_to_xeb_fidelity,
22
- decay_constant_to_pauli_error,
23
- pauli_error_to_decay_constant,
24
- xeb_fidelity_to_decay_constant,
25
- pauli_error_from_t1,
26
- average_error,
27
- decoherence_pauli_error,
28
- )
16
+ from cirq.devices.noise_utils import OpIdentifier
29
17
 
30
18
 
31
19
  def test_op_identifier():
@@ -67,74 +55,3 @@ def test_op_id_instance():
67
55
  gate = cirq.SingleQubitCliffordGate.from_xz_map((cirq.X, False), (cirq.Z, False))
68
56
  op_id = OpIdentifier(gate, q0)
69
57
  cirq.testing.assert_equivalent_repr(op_id)
70
-
71
-
72
- @pytest.mark.parametrize(
73
- 'decay_constant,num_qubits,expected_output',
74
- [(0.01, 1, 1 - (0.99 * 1 / 2)), (0.05, 2, 1 - (0.95 * 3 / 4))],
75
- )
76
- def test_decay_constant_to_xeb_fidelity(decay_constant, num_qubits, expected_output):
77
- val = decay_constant_to_xeb_fidelity(decay_constant, num_qubits)
78
- assert val == expected_output
79
-
80
-
81
- @pytest.mark.parametrize(
82
- 'decay_constant,num_qubits,expected_output',
83
- [(0.01, 1, 0.99 * 3 / 4), (0.05, 2, 0.95 * 15 / 16)],
84
- )
85
- def test_decay_constant_to_pauli_error(decay_constant, num_qubits, expected_output):
86
- val = decay_constant_to_pauli_error(decay_constant, num_qubits)
87
- assert val == expected_output
88
-
89
-
90
- @pytest.mark.parametrize(
91
- 'pauli_error,num_qubits,expected_output',
92
- [(0.01, 1, 1 - (0.01 / (3 / 4))), (0.05, 2, 1 - (0.05 / (15 / 16)))],
93
- )
94
- def test_pauli_error_to_decay_constant(pauli_error, num_qubits, expected_output):
95
- val = pauli_error_to_decay_constant(pauli_error, num_qubits)
96
- assert val == expected_output
97
-
98
-
99
- @pytest.mark.parametrize(
100
- 'xeb_fidelity,num_qubits,expected_output',
101
- [(0.01, 1, 1 - 0.99 / (1 / 2)), (0.05, 2, 1 - 0.95 / (3 / 4))],
102
- )
103
- def test_xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits, expected_output):
104
- val = xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits)
105
- assert val == expected_output
106
-
107
-
108
- @pytest.mark.parametrize(
109
- 't,t1_ns,expected_output',
110
- [
111
- (20, 1e5, (1 - np.exp(-20 / 2e5)) / 2 + (1 - np.exp(-20 / 1e5)) / 4),
112
- (4000, 1e4, (1 - np.exp(-4000 / 2e4)) / 2 + (1 - np.exp(-4000 / 1e4)) / 4),
113
- ],
114
- )
115
- def test_pauli_error_from_t1(t, t1_ns, expected_output):
116
- val = pauli_error_from_t1(t, t1_ns)
117
- assert val == expected_output
118
-
119
-
120
- @pytest.mark.parametrize(
121
- 'decay_constant,num_qubits,expected_output', [(0.01, 1, 0.99 * 1 / 2), (0.05, 2, 0.95 * 3 / 4)]
122
- )
123
- def test_average_error(decay_constant, num_qubits, expected_output):
124
- val = average_error(decay_constant, num_qubits)
125
- assert val == expected_output
126
-
127
-
128
- @pytest.mark.parametrize(
129
- 'T1_ns,Tphi_ns,gate_time_ns', [(1e4, 2e4, 25), (1e5, 2e3, 25), (1e4, 2e4, 4000)]
130
- )
131
- def test_decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns):
132
- val = decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns)
133
- # Expected value is of the form:
134
- #
135
- # (1/4) * [1 - e^(-t/T1)] + (1/2) * [1 - e^(-t/(2*T1) - t/Tphi]
136
- #
137
- expected_output = 0.25 * (1 - np.exp(-gate_time_ns / T1_ns)) + 0.5 * (
138
- 1 - np.exp(-gate_time_ns * ((1 / (2 * T1_ns)) + 1 / Tphi_ns))
139
- )
140
- assert val == expected_output