qiskit 2.0.0rc1__cp39-abi3-macosx_11_0_arm64.whl → 2.0.1__cp39-abi3-macosx_11_0_arm64.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.
Files changed (57) hide show
  1. qiskit/VERSION.txt +1 -1
  2. qiskit/__init__.py +1 -4
  3. qiskit/_accelerate.abi3.so +0 -0
  4. qiskit/circuit/__init__.py +11 -5
  5. qiskit/circuit/classical/expr/constructors.py +0 -12
  6. qiskit/circuit/library/__init__.py +449 -163
  7. qiskit/circuit/library/graph_state.py +1 -0
  8. qiskit/circuit/library/n_local/efficient_su2.py +1 -1
  9. qiskit/circuit/library/n_local/excitation_preserving.py +1 -1
  10. qiskit/circuit/library/quantum_volume.py +9 -0
  11. qiskit/circuit/library/standard_gates/r.py +5 -4
  12. qiskit/circuit/library/standard_gates/x.py +1 -2
  13. qiskit/circuit/parameterexpression.py +7 -0
  14. qiskit/circuit/quantumcircuit.py +405 -80
  15. qiskit/circuit/tools/pi_check.py +3 -0
  16. qiskit/converters/circuit_to_dag.py +2 -2
  17. qiskit/converters/dag_to_circuit.py +2 -3
  18. qiskit/dagcircuit/dagdependency_v2.py +3 -2
  19. qiskit/primitives/statevector_estimator.py +1 -1
  20. qiskit/qpy/__init__.py +21 -0
  21. qiskit/qpy/binary_io/circuits.py +5 -0
  22. qiskit/qpy/binary_io/parse_sympy_repr.py +121 -0
  23. qiskit/qpy/binary_io/value.py +5 -5
  24. qiskit/result/models.py +1 -2
  25. qiskit/result/result.py +10 -8
  26. qiskit/synthesis/discrete_basis/commutator_decompose.py +30 -6
  27. qiskit/synthesis/discrete_basis/gate_sequence.py +10 -4
  28. qiskit/synthesis/discrete_basis/generate_basis_approximations.py +1 -1
  29. qiskit/synthesis/discrete_basis/solovay_kitaev.py +36 -13
  30. qiskit/transpiler/passes/__init__.py +2 -0
  31. qiskit/transpiler/passes/basis/basis_translator.py +1 -1
  32. qiskit/transpiler/passes/layout/full_ancilla_allocation.py +2 -3
  33. qiskit/transpiler/passes/layout/sabre_layout.py +3 -1
  34. qiskit/transpiler/passes/layout/vf2_utils.py +2 -5
  35. qiskit/transpiler/passes/optimization/__init__.py +1 -0
  36. qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +2 -2
  37. qiskit/transpiler/passes/scheduling/alignments/check_durations.py +1 -1
  38. qiskit/transpiler/passes/scheduling/padding/base_padding.py +2 -2
  39. qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +5 -5
  40. qiskit/transpiler/passes/scheduling/padding/pad_delay.py +1 -1
  41. qiskit/transpiler/passes/scheduling/time_unit_conversion.py +10 -6
  42. qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py +29 -19
  43. qiskit/transpiler/passes/synthesis/unitary_synthesis.py +2 -1
  44. qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +13 -7
  45. qiskit/transpiler/target.py +11 -0
  46. qiskit/visualization/circuit/text.py +1 -1
  47. qiskit/visualization/counts_visualization.py +4 -0
  48. qiskit/visualization/library.py +4 -1
  49. qiskit/visualization/state_visualization.py +13 -2
  50. qiskit/visualization/timeline/core.py +1 -1
  51. qiskit/visualization/timeline/plotters/matplotlib.py +4 -1
  52. {qiskit-2.0.0rc1.dist-info → qiskit-2.0.1.dist-info}/METADATA +4 -3
  53. {qiskit-2.0.0rc1.dist-info → qiskit-2.0.1.dist-info}/RECORD +57 -56
  54. {qiskit-2.0.0rc1.dist-info → qiskit-2.0.1.dist-info}/WHEEL +1 -1
  55. {qiskit-2.0.0rc1.dist-info → qiskit-2.0.1.dist-info}/entry_points.txt +0 -0
  56. {qiskit-2.0.0rc1.dist-info → qiskit-2.0.1.dist-info/licenses}/LICENSE.txt +0 -0
  57. {qiskit-2.0.0rc1.dist-info → qiskit-2.0.1.dist-info}/top_level.txt +0 -0
@@ -49,7 +49,10 @@ def pi_check(inpt, eps=1e-9, output="text", ndigits=None):
49
49
  if isinstance(inpt, ParameterExpression):
50
50
  param_str = str(inpt)
51
51
  from sympy import sympify
52
+ import symengine
52
53
 
54
+ if not isinstance(inpt._symbol_expr, symengine.Basic):
55
+ raise QiskitError("Invalid ParameterExpression provided")
53
56
  expr = sympify(inpt._symbol_expr)
54
57
  syms = expr.atoms()
55
58
  for sym in syms:
@@ -75,6 +75,6 @@ def circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_ord
75
75
 
76
76
  dagcircuit = core_circuit_to_dag(circuit, copy_operations, qubit_order, clbit_order)
77
77
 
78
- dagcircuit.duration = circuit._duration
79
- dagcircuit.unit = circuit._unit
78
+ dagcircuit._duration = circuit._duration
79
+ dagcircuit._unit = circuit._unit
80
80
  return dagcircuit
@@ -74,7 +74,6 @@ def dag_to_circuit(dag, copy_operations=True):
74
74
  circuit.add_stretch(stretch)
75
75
  circuit.metadata = dag.metadata or {}
76
76
  circuit._data = circuit_data
77
-
78
- circuit._duration = dag.duration
79
- circuit._unit = dag.unit
77
+ circuit._duration = dag._duration
78
+ circuit._unit = dag._unit
80
79
  return circuit
@@ -447,8 +447,6 @@ class _DAGDependencyV2:
447
447
  target_dag = _DAGDependencyV2()
448
448
  target_dag.name = self.name
449
449
  target_dag._global_phase = self._global_phase
450
- target_dag.duration = self.duration
451
- target_dag.unit = self.unit
452
450
  target_dag.metadata = self.metadata
453
451
  target_dag._key_cache = self._key_cache
454
452
  target_dag.comm_checker = self.comm_checker
@@ -456,6 +454,9 @@ class _DAGDependencyV2:
456
454
  target_dag.add_qubits(self.qubits)
457
455
  target_dag.add_clbits(self.clbits)
458
456
 
457
+ target_dag.duration = self.duration
458
+ target_dag.unit = self.unit
459
+
459
460
  for qreg in self.qregs.values():
460
461
  target_dag.add_qreg(qreg)
461
462
  for creg in self.cregs.values():
@@ -33,7 +33,7 @@ class StatevectorEstimator(BaseEstimatorV2):
33
33
  Simple implementation of :class:`BaseEstimatorV2` with full state vector simulation.
34
34
 
35
35
  This class is implemented via :class:`~.Statevector` which turns provided circuits into
36
- pure state vectors. These states are subsequently acted on by :class:~.SparsePauliOp`,
36
+ pure state vectors. These states are subsequently acted on by :class:`~.SparsePauliOp`,
37
37
  which implies that, at present, this implementation is only compatible with Pauli-based
38
38
  observables.
39
39
 
qiskit/qpy/__init__.py CHANGED
@@ -194,6 +194,27 @@ of QPY in qiskit-terra 0.18.0.
194
194
  * - Qiskit (qiskit-terra for < 1.0.0) version
195
195
  - :func:`.dump` format(s) output versions
196
196
  - :func:`.load` maximum supported version (older format versions can always be read)
197
+ * - 2.0.0
198
+ - 13, 14
199
+ - 14
200
+ * - 1.4.2
201
+ - 10, 11, 12, 13
202
+ - 13
203
+ * - 1.4.1
204
+ - 10, 11, 12, 13
205
+ - 13
206
+ * - 1.4.0
207
+ - 10, 11, 12, 13
208
+ - 13
209
+ * - 1.3.3
210
+ - 10, 11, 12, 13
211
+ - 13
212
+ * - 1.3.2
213
+ - 10, 11, 12, 13
214
+ - 13
215
+ * - 1.3.1
216
+ - 10, 11, 12, 13
217
+ - 13
197
218
  * - 1.3.0
198
219
  - 10, 11, 12, 13
199
220
  - 13
@@ -783,6 +783,11 @@ def _write_instruction(
783
783
  custom_operations[gate_class_name] = instruction.operation
784
784
  custom_operations_list.append(gate_class_name)
785
785
 
786
+ elif isinstance(instruction.operation, library.MCMTGate):
787
+ gate_class_name = instruction.operation.name + "_" + str(uuid.uuid4())
788
+ custom_operations[gate_class_name] = instruction.operation
789
+ custom_operations_list.append(gate_class_name)
790
+
786
791
  condition_type = type_keys.Condition.NONE
787
792
  condition_register = b""
788
793
  condition_value = 0
@@ -0,0 +1,121 @@
1
+ # This code is part of Qiskit.
2
+ #
3
+ # (C) Copyright IBM 2025.
4
+ #
5
+ # This code is licensed under the Apache License, Version 2.0. You may
6
+ # obtain a copy of this license in the LICENSE.txt file in the root directory
7
+ # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
+ #
9
+ # Any modifications or derivative works of this code must retain this
10
+ # copyright notice, and modified files need to carry a notice indicating
11
+ # that they have been altered from the originals.
12
+
13
+ """Parser for sympy expressions srepr from ParameterExpression internals."""
14
+
15
+ import ast
16
+
17
+ from qiskit.qpy.exceptions import QpyError
18
+
19
+
20
+ ALLOWED_CALLERS = {
21
+ "Abs",
22
+ "Add",
23
+ "Sub",
24
+ "Mul",
25
+ "Div",
26
+ "Pow",
27
+ "Symbol",
28
+ "Integer",
29
+ "Rational",
30
+ "Complex",
31
+ "Float",
32
+ "log",
33
+ "sin",
34
+ "cos",
35
+ "tan",
36
+ "atan",
37
+ "acos",
38
+ "asin",
39
+ "exp",
40
+ "conjugate",
41
+ }
42
+
43
+ UNARY = {
44
+ "sin",
45
+ "cos",
46
+ "tan",
47
+ "atan",
48
+ "acos",
49
+ "asin",
50
+ "conjugate",
51
+ "Symbol",
52
+ "Integer",
53
+ "Complex",
54
+ "Abs",
55
+ "Float",
56
+ }
57
+
58
+
59
+ class ParseSympyWalker(ast.NodeVisitor):
60
+ """A custom ast walker that is passed the sympy srepr from QPY < 13 and creates a custom
61
+ expression."""
62
+
63
+ def __init__(self):
64
+ self.stack = []
65
+
66
+ def visit_UnaryOp(self, node: ast.UnaryOp): # pylint: disable=invalid-name
67
+ """Visit a python unary op node"""
68
+ self.visit(node.operand)
69
+ arg = self.stack.pop()
70
+ if isinstance(node.op, ast.UAdd):
71
+ self.stack.append(+arg)
72
+ elif isinstance(node.op, ast.USub):
73
+ self.stack.append(-arg)
74
+ elif isinstance(node.op, ast.Not):
75
+ self.stack.append(not arg)
76
+ elif isinstance(node.op, ast.Invert):
77
+ self.stack.append(~arg)
78
+ else:
79
+ raise QpyError(f"Invalid unary op as part of sympy srepr: {node.op}")
80
+
81
+ def visit_Constant(self, node: ast.Constant): # pylint: disable=invalid-name
82
+ """Visit a constant node."""
83
+ self.stack.append(node.value)
84
+
85
+ def visit_Call(self, node: ast.Call): # pylint: disable=invalid-name
86
+ """Visit a call node
87
+
88
+ This can only be parameter expression allowed sympy call types.
89
+ """
90
+ import sympy
91
+
92
+ if isinstance(node.func, ast.Name):
93
+ name = node.func.id
94
+ else:
95
+ raise QpyError("Unknown node type")
96
+
97
+ if name not in ALLOWED_CALLERS:
98
+ raise QpyError(f"{name} is not part of a valid sympy expression srepr")
99
+
100
+ args = node.args
101
+ if name in UNARY:
102
+ if len(args) != 1:
103
+ raise QpyError(f"{name} has an invalid number of args in sympy srepr")
104
+ self.visit(args[0])
105
+ obj = getattr(sympy, name)(self.stack.pop())
106
+ self.stack.append(obj)
107
+ else:
108
+ for arg in args:
109
+ self.visit(arg)
110
+ out_args = [self.stack.pop() for _ in range(len(args))]
111
+ out_args.reverse()
112
+ obj = getattr(sympy, name)(*out_args)
113
+ self.stack.append(obj)
114
+
115
+
116
+ def parse_sympy_repr(sympy_repr: str):
117
+ """Parse a given sympy srepr into a symbolic expression object."""
118
+ tree = ast.parse(sympy_repr, mode="eval")
119
+ visitor = ParseSympyWalker()
120
+ visitor.visit(tree)
121
+ return visitor.stack.pop()
@@ -34,6 +34,7 @@ from qiskit.circuit.parameterexpression import (
34
34
  )
35
35
  from qiskit.circuit.parametervector import ParameterVector, ParameterVectorElement
36
36
  from qiskit.qpy import common, formats, exceptions, type_keys
37
+ from qiskit.qpy.binary_io.parse_sympy_repr import parse_sympy_repr
37
38
 
38
39
 
39
40
  def _write_parameter(file_obj, obj):
@@ -473,9 +474,9 @@ def _read_parameter_expression(file_obj):
473
474
  data = formats.PARAMETER_EXPR(
474
475
  *struct.unpack(formats.PARAMETER_EXPR_PACK, file_obj.read(formats.PARAMETER_EXPR_SIZE))
475
476
  )
476
- from sympy.parsing.sympy_parser import parse_expr
477
477
 
478
- expr_ = symengine.sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)))
478
+ sympy_str = file_obj.read(data.expr_size).decode(common.ENCODE)
479
+ expr_ = symengine.sympify(parse_sympy_repr(sympy_str))
479
480
  symbol_map = {}
480
481
  for _ in range(data.map_elements):
481
482
  elem_data = formats.PARAM_EXPR_MAP_ELEM(
@@ -514,9 +515,8 @@ def _read_parameter_expression_v3(file_obj, vectors, use_symengine):
514
515
  if use_symengine:
515
516
  expr_ = common.load_symengine_payload(payload)
516
517
  else:
517
- from sympy.parsing.sympy_parser import parse_expr
518
-
519
- expr_ = symengine.sympify(parse_expr(payload.decode(common.ENCODE)))
518
+ sympy_str = payload.decode(common.ENCODE)
519
+ expr_ = symengine.sympify(parse_sympy_repr(sympy_str))
520
520
 
521
521
  symbol_map = {}
522
522
  for _ in range(data.map_elements):
qiskit/result/models.py CHANGED
@@ -145,8 +145,7 @@ class ExperimentResult:
145
145
  status (str): The status of the experiment
146
146
  seed (int): The seed used for simulation (if run on a simulator)
147
147
  meas_return (str): The type of measurement returned
148
- header (dict): A free form dictionary
149
- header for the experiment
148
+ header (dict): A free form dictionary header for the experiment
150
149
  kwargs: Arbitrary extra fields
151
150
 
152
151
  Raises:
qiskit/result/result.py CHANGED
@@ -34,6 +34,9 @@ class Result:
34
34
  each experiment success)
35
35
  results (list[ExperimentResult]): corresponding results for array of
36
36
  experiments of the input
37
+ date (str): optional date field
38
+ status (str): optional status field
39
+ header (dict): an optional free form dictionary header
37
40
  """
38
41
 
39
42
  _metadata = {}
@@ -88,7 +91,7 @@ class Result:
88
91
  "backend_name": self.backend_name,
89
92
  "backend_version": self.backend_version,
90
93
  "date": self.date,
91
- "header": None if self.header is None else self.header.to_dict(),
94
+ "header": self.header,
92
95
  "job_id": self.job_id,
93
96
  "status": self.status,
94
97
  "success": self.success,
@@ -128,11 +131,10 @@ class Result:
128
131
  the get_xxx method, and the data will be post-processed for the data type.
129
132
 
130
133
  Args:
131
- experiment (str or QuantumCircuit or Schedule or int or None): the index of the
134
+ experiment (str or QuantumCircuit or int or None): the index of the
132
135
  experiment. Several types are accepted for convenience::
133
136
  * str: the name of the experiment.
134
137
  * QuantumCircuit: the name of the circuit instance will be used.
135
- * Schedule: the name of the schedule instance will be used.
136
138
  * int: the position of the experiment.
137
139
  * None: if there is only one experiment, returns it.
138
140
 
@@ -179,7 +181,7 @@ class Result:
179
181
  ['00000', '01000', '10100', '10100', '11101', '11100', '00101', ..., '01010']
180
182
 
181
183
  Args:
182
- experiment (str or QuantumCircuit or Schedule or int or None): the index of the
184
+ experiment (str or QuantumCircuit or int or None): the index of the
183
185
  experiment, as specified by ``data()``.
184
186
 
185
187
  Returns:
@@ -231,7 +233,7 @@ class Result:
231
233
  """Get the histogram data of an experiment.
232
234
 
233
235
  Args:
234
- experiment (str or QuantumCircuit or Schedule or int or None): the index of the
236
+ experiment (str or QuantumCircuit or int or None): the index of the
235
237
  experiment, as specified by ``data([experiment])``.
236
238
 
237
239
  Returns:
@@ -283,7 +285,7 @@ class Result:
283
285
  """Get the final statevector of an experiment.
284
286
 
285
287
  Args:
286
- experiment (str or QuantumCircuit or Schedule or int or None): the index of the
288
+ experiment (str or QuantumCircuit or int or None): the index of the
287
289
  experiment, as specified by ``data()``.
288
290
  decimals (int): the number of decimals in the statevector.
289
291
  If None, does not round.
@@ -305,7 +307,7 @@ class Result:
305
307
  """Get the final unitary of an experiment.
306
308
 
307
309
  Args:
308
- experiment (str or QuantumCircuit or Schedule or int or None): the index of the
310
+ experiment (str or QuantumCircuit or int or None): the index of the
309
311
  experiment, as specified by ``data()``.
310
312
  decimals (int): the number of decimals in the unitary.
311
313
  If None, does not round.
@@ -326,7 +328,7 @@ class Result:
326
328
  """Return a single experiment result from a given key.
327
329
 
328
330
  Args:
329
- key (str or QuantumCircuit or Schedule or int or None): the index of the
331
+ key (str or QuantumCircuit or int or None): the index of the
330
332
  experiment, as specified by ``data()``.
331
333
 
332
334
  Returns:
@@ -53,16 +53,40 @@ def _compute_rotation_axis(matrix: np.ndarray) -> np.ndarray:
53
53
  """
54
54
  _check_is_so3(matrix)
55
55
 
56
+ # If theta represents the rotation angle, then trace = 1 + 2cos(theta).
56
57
  trace = _compute_trace_so3(matrix)
57
- theta = math.acos(0.5 * (trace - 1))
58
- if math.sin(theta) > 1e-10:
59
- x = 1 / (2 * math.sin(theta)) * (matrix[2][1] - matrix[1][2])
60
- y = 1 / (2 * math.sin(theta)) * (matrix[0][2] - matrix[2][0])
61
- z = 1 / (2 * math.sin(theta)) * (matrix[1][0] - matrix[0][1])
62
- else:
58
+
59
+ if trace >= 3 - 1e-10:
60
+ # The matrix is the identity (rotation by 0)
63
61
  x = 1.0
64
62
  y = 0.0
65
63
  z = 0.0
64
+
65
+ elif trace <= -1 + 1e-10:
66
+ # The matrix is the 180-degree rotation
67
+ squares = (1 + np.diagonal(matrix)) / 2
68
+ index_of_max = np.argmax(squares)
69
+
70
+ if index_of_max == 0:
71
+ x = math.sqrt(squares[0])
72
+ y = matrix[0][1] / (2 * x)
73
+ z = matrix[0][2] / (2 * x)
74
+ elif index_of_max == 1:
75
+ y = math.sqrt(squares[1])
76
+ x = matrix[0][1] / (2 * y)
77
+ z = matrix[1][2] / (2 * y)
78
+ else:
79
+ z = math.sqrt(squares[2])
80
+ x = matrix[0][2] / (2 * z)
81
+ y = matrix[1][2] / (2 * z)
82
+
83
+ else:
84
+ # The matrix is the rotation by theta with sin(theta)!=0
85
+ theta = math.acos(0.5 * (trace - 1))
86
+ x = 1 / (2 * math.sin(theta)) * (matrix[2][1] - matrix[1][2])
87
+ y = 1 / (2 * math.sin(theta)) * (matrix[0][2] - matrix[2][0])
88
+ z = 1 / (2 * math.sin(theta)) * (matrix[1][0] - matrix[0][1])
89
+
66
90
  return np.array([x, y, z])
67
91
 
68
92
 
@@ -55,7 +55,6 @@ class GateSequence:
55
55
  self.name = " ".join(self.labels)
56
56
  self.global_phase = global_phase
57
57
  self.product = so3_matrix
58
- self.product_su2 = su2_matrix
59
58
 
60
59
  def remove_cancelling_pair(self, indices: Sequence[int]) -> None:
61
60
  """Remove a pair of indices that cancel each other and *do not* change the matrices."""
@@ -106,6 +105,16 @@ class GateSequence:
106
105
 
107
106
  return circuit
108
107
 
108
+ def _to_u2(self):
109
+ """Creates the U2 matrix corresponding to the stored sequence of gates
110
+ and the global phase.
111
+ """
112
+ u2 = np.eye(2, dtype=complex)
113
+ for mat in self.gates:
114
+ u2 = mat.to_matrix().dot(u2)
115
+ u2 = np.exp(1j * self.global_phase) * u2
116
+ return u2
117
+
109
118
  def to_dag(self):
110
119
  """Convert to a :class:`.DAGCircuit`.
111
120
 
@@ -149,7 +158,6 @@ class GateSequence:
149
158
  so3 = _convert_su2_to_so3(su2)
150
159
 
151
160
  self.product = so3.dot(self.product)
152
- self.product_su2 = su2.dot(self.product_su2)
153
161
  self.global_phase = self.global_phase + phase
154
162
 
155
163
  self.gates.append(gate)
@@ -172,7 +180,6 @@ class GateSequence:
172
180
  adjoint.labels = [inv.name for inv in adjoint.gates]
173
181
  adjoint.name = " ".join(adjoint.labels)
174
182
  adjoint.product = np.conj(self.product).T
175
- adjoint.product_su2 = np.conj(self.product_su2).T
176
183
  adjoint.global_phase = -self.global_phase
177
184
 
178
185
  return adjoint
@@ -190,7 +197,6 @@ class GateSequence:
190
197
  out.matrices = self.matrices.copy()
191
198
  out.global_phase = self.global_phase
192
199
  out.product = self.product.copy()
193
- out.product_su2 = self.product_su2.copy()
194
200
  out.name = self.name
195
201
  out._eulers = self._eulers
196
202
  return out
@@ -76,7 +76,7 @@ def _check_candidate_greedy(candidate, existing_sequences, tol=1e-10):
76
76
  return False
77
77
 
78
78
  for existing in existing_sequences:
79
- if matrix_equal(existing.product_su2, candidate.product_su2, ignore_phase=True, atol=tol):
79
+ if matrix_equal(existing.product, candidate.product, ignore_phase=True, atol=tol):
80
80
  # is the new sequence less or more efficient?
81
81
  return len(candidate.gates) < len(existing.gates)
82
82
  return True
@@ -24,7 +24,7 @@ from .generate_basis_approximations import generate_basic_approximations, _1q_ga
24
24
  class SolovayKitaevDecomposition:
25
25
  """The Solovay Kitaev discrete decomposition algorithm.
26
26
 
27
- This class is called recursively by the transpiler pass, which is why it is separeted.
27
+ This class is called recursively by the transpiler pass, which is why it is separated.
28
28
  See :class:`qiskit.transpiler.passes.SolovayKitaev` for more information.
29
29
  """
30
30
 
@@ -33,7 +33,7 @@ class SolovayKitaevDecomposition:
33
33
  ) -> None:
34
34
  """
35
35
  Args:
36
- basic_approximations: A specification of the basic SU(2) approximations in terms
36
+ basic_approximations: A specification of the basic SO(3) approximations in terms
37
37
  of discrete gates. At each iteration this algorithm, the remaining error is
38
38
  approximated with the closest sequence of gates in this set.
39
39
  If a ``str``, this specifies a ``.npy`` filename from which to load the
@@ -116,23 +116,33 @@ class SolovayKitaevDecomposition:
116
116
  """
117
117
  # make input matrix SU(2) and get the according global phase
118
118
  z = 1 / np.sqrt(np.linalg.det(gate_matrix))
119
- gate_matrix_su2 = GateSequence.from_matrix(z * gate_matrix)
119
+
120
+ gate_matrix_su2 = z * gate_matrix
121
+ gate_matrix_as_sequence = GateSequence.from_matrix(gate_matrix_su2)
120
122
  global_phase = np.arctan2(np.imag(z), np.real(z))
121
123
 
122
124
  # get the decomposition as GateSequence type
123
- decomposition = self._recurse(gate_matrix_su2, recursion_degree, check_input=check_input)
125
+ decomposition = self._recurse(
126
+ gate_matrix_as_sequence, recursion_degree, check_input=check_input
127
+ )
124
128
 
125
129
  # simplify
126
130
  _remove_identities(decomposition)
127
131
  _remove_inverse_follows_gate(decomposition)
128
132
 
133
+ # adjust to the correct SU(2) phase
134
+ adjust_phase = (
135
+ np.pi if _should_adjust_phase(decomposition._to_u2(), gate_matrix_su2) else 0.0
136
+ )
137
+
129
138
  # convert to a circuit and attach the right phases
130
139
  if return_dag:
131
140
  out = decomposition.to_dag()
132
141
  else:
133
142
  out = decomposition.to_circuit()
134
143
 
135
- out.global_phase = decomposition.global_phase - global_phase
144
+ out.global_phase += adjust_phase
145
+ out.global_phase -= global_phase
136
146
 
137
147
  return out
138
148
 
@@ -155,17 +165,20 @@ class SolovayKitaevDecomposition:
155
165
  raise ValueError("Shape of U must be (3, 3) but is", sequence.shape)
156
166
 
157
167
  if n == 0:
158
- return self.find_basic_approximation(sequence)
168
+ res = self.find_basic_approximation(sequence)
159
169
 
160
- u_n1 = self._recurse(sequence, n - 1, check_input=check_input)
170
+ else:
171
+ u_n1 = self._recurse(sequence, n - 1, check_input=check_input)
161
172
 
162
- v_n, w_n = commutator_decompose(
163
- sequence.dot(u_n1.adjoint()).product, check_input=check_input
164
- )
173
+ v_n, w_n = commutator_decompose(
174
+ sequence.dot(u_n1.adjoint()).product, check_input=check_input
175
+ )
176
+
177
+ v_n1 = self._recurse(v_n, n - 1, check_input=check_input)
178
+ w_n1 = self._recurse(w_n, n - 1, check_input=check_input)
179
+ res = v_n1.dot(w_n1).dot(v_n1.adjoint()).dot(w_n1.adjoint()).dot(u_n1)
165
180
 
166
- v_n1 = self._recurse(v_n, n - 1, check_input=check_input)
167
- w_n1 = self._recurse(w_n, n - 1, check_input=check_input)
168
- return v_n1.dot(w_n1).dot(v_n1.adjoint()).dot(w_n1.adjoint()).dot(u_n1)
181
+ return res
169
182
 
170
183
  def find_basic_approximation(self, sequence: GateSequence) -> GateSequence:
171
184
  """Find ``GateSequence`` in ``self._basic_approximations`` that approximates ``sequence``.
@@ -215,3 +228,13 @@ def _remove_identities(sequence):
215
228
  sequence.gates.pop(index)
216
229
  else:
217
230
  index += 1
231
+
232
+
233
+ def _should_adjust_phase(computed: np.ndarray, target: np.ndarray) -> bool:
234
+ """
235
+ The implemented SolovayKitaevDecomposition has a global phase uncertainty of +-1,
236
+ due to approximating not the original SU(2) matrix but its projection onto SO(3).
237
+ This function returns ``True`` if the global phase of the computed approximation
238
+ should be adjusted (by adding pi) to better much the target.
239
+ """
240
+ return np.linalg.norm(-computed - target) < np.linalg.norm(computed - target)
@@ -91,6 +91,7 @@ Optimizations
91
91
  Split2QUnitaries
92
92
  RemoveIdentityEquivalent
93
93
  ContractIdleWiresInControlFlow
94
+ LightCone
94
95
 
95
96
  Scheduling
96
97
  =============
@@ -225,6 +226,7 @@ from .optimization import OptimizeAnnotated
225
226
  from .optimization import RemoveIdentityEquivalent
226
227
  from .optimization import Split2QUnitaries
227
228
  from .optimization import ContractIdleWiresInControlFlow
229
+ from .optimization import LightCone
228
230
 
229
231
  # circuit analysis
230
232
  from .analysis import ResourceEstimation
@@ -18,7 +18,7 @@ import logging
18
18
  from collections import defaultdict
19
19
 
20
20
  from qiskit.transpiler.basepasses import TransformationPass
21
- from qiskit._accelerate.basis.basis_translator import base_run
21
+ from qiskit._accelerate.basis_translator import base_run
22
22
 
23
23
  logger = logging.getLogger(__name__)
24
24
 
@@ -92,9 +92,8 @@ class FullAncillaAllocation(AnalysisPass):
92
92
 
93
93
  if idle_physical_qubits:
94
94
  if self.ancilla_name in dag.qregs:
95
- qreg = QuantumRegister(
96
- len(idle_physical_qubits),
97
- name=f"{self.ancilla_name}{QuantumRegister.instances_count + 1}",
95
+ qreg = QuantumRegister._new_with_prefix(
96
+ len(idle_physical_qubits), self.ancilla_name
98
97
  )
99
98
  else:
100
99
  qreg = QuantumRegister(len(idle_physical_qubits), name=self.ancilla_name)
@@ -305,6 +305,9 @@ class SabreLayout(TransformationPass):
305
305
  # the layout and routing together as part of resolving the Sabre result.
306
306
  physical_qubits = QuantumRegister(self.coupling_map.size(), "q")
307
307
  mapped_dag = DAGCircuit()
308
+ mapped_dag.name = dag.name
309
+ mapped_dag.metadata = dag.metadata
310
+ mapped_dag.global_phase = dag.global_phase
308
311
  mapped_dag.add_qreg(physical_qubits)
309
312
  mapped_dag.add_clbits(dag.clbits)
310
313
  for creg in dag.cregs.values():
@@ -319,7 +322,6 @@ class SabreLayout(TransformationPass):
319
322
  mapped_dag.add_captured_stretch(stretch)
320
323
  for stretch in dag.iter_declared_stretches():
321
324
  mapped_dag.add_declared_stretch(stretch)
322
- mapped_dag.global_phase = dag.global_phase
323
325
  self.property_set["original_qubit_indices"] = {
324
326
  bit: index for index, bit in enumerate(dag.qubits)
325
327
  }
@@ -179,11 +179,8 @@ def build_average_error_map(target, coupling_map):
179
179
  coupling_map = target.build_coupling_map()
180
180
  if not built and coupling_map is not None and num_qubits is not None:
181
181
  for qubit in range(num_qubits):
182
- avg_map.add_error(
183
- (qubit, qubit),
184
- (coupling_map.graph.out_degree(qubit) + coupling_map.graph.in_degree(qubit))
185
- / num_qubits,
186
- )
182
+ degree = len(set(coupling_map.graph.neighbors_undirected(qubit)))
183
+ avg_map.add_error((qubit, qubit), degree / num_qubits)
187
184
  for edge in coupling_map.graph.edge_list():
188
185
  avg_map.add_error(edge, (avg_map[edge[0], edge[0]] + avg_map[edge[1], edge[1]]) / 2)
189
186
  built = True
@@ -39,3 +39,4 @@ from .remove_identity_equiv import RemoveIdentityEquivalent
39
39
  from .split_2q_unitaries import Split2QUnitaries
40
40
  from .collect_and_collapse import CollectAndCollapse
41
41
  from .contract_idle_wires_in_control_flow import ContractIdleWiresInControlFlow
42
+ from .light_cone import LightCone
@@ -496,7 +496,6 @@ class TemplateSubstitution:
496
496
  parameter constraints, returns None.
497
497
  """
498
498
  import sympy as sym
499
- from sympy.parsing.sympy_parser import parse_expr
500
499
 
501
500
  if _optionals.HAS_SYMENGINE:
502
501
  import symengine
@@ -572,7 +571,8 @@ class TemplateSubstitution:
572
571
  if isinstance(circuit_param, ParameterExpression):
573
572
  circ_param_sym = circuit_param.sympify()
574
573
  else:
575
- circ_param_sym = parse_expr(str(circuit_param))
574
+ # if it's not a ParameterExpression we're a float
575
+ circ_param_sym = sym.Float(circuit_param)
576
576
  equations.append(sym.Eq(template_param.sympify(), circ_param_sym))
577
577
 
578
578
  for param in template_param.parameters:
@@ -59,7 +59,7 @@ class InstructionDurationCheck(AnalysisPass):
59
59
  self.property_set["reschedule_required"] = False
60
60
 
61
61
  # Rescheduling is not necessary
62
- if self.acquire_align == 1 and self.pulse_align == 1:
62
+ if (self.acquire_align == 1 and self.pulse_align == 1) or dag._num_stretches != 0:
63
63
  return
64
64
 
65
65
  # Check delay durations