qiskit 1.4.1__cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl → 1.4.3__cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.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 (39) hide show
  1. qiskit/VERSION.txt +1 -1
  2. qiskit/_accelerate.abi3.so +0 -0
  3. qiskit/circuit/duration.py +16 -16
  4. qiskit/circuit/library/standard_gates/r.py +4 -3
  5. qiskit/circuit/library/standard_gates/x.py +1 -2
  6. qiskit/circuit/parameterexpression.py +6 -1
  7. qiskit/circuit/quantumcircuit.py +9 -9
  8. qiskit/circuit/tools/pi_check.py +3 -0
  9. qiskit/converters/circuit_to_dag.py +2 -2
  10. qiskit/converters/dag_to_circuit.py +2 -3
  11. qiskit/dagcircuit/dagdependency_v2.py +3 -2
  12. qiskit/primitives/statevector_estimator.py +1 -1
  13. qiskit/qpy/binary_io/circuits.py +21 -4
  14. qiskit/qpy/binary_io/parse_sympy_repr.py +121 -0
  15. qiskit/qpy/binary_io/schedules.py +61 -18
  16. qiskit/qpy/binary_io/value.py +8 -5
  17. qiskit/qpy/interface.py +19 -5
  18. qiskit/synthesis/discrete_basis/commutator_decompose.py +30 -6
  19. qiskit/synthesis/discrete_basis/gate_sequence.py +10 -4
  20. qiskit/synthesis/discrete_basis/generate_basis_approximations.py +3 -1
  21. qiskit/synthesis/discrete_basis/solovay_kitaev.py +36 -13
  22. qiskit/transpiler/passes/layout/sabre_layout.py +4 -1
  23. qiskit/transpiler/passes/layout/vf2_utils.py +2 -5
  24. qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +2 -2
  25. qiskit/transpiler/passes/scheduling/alap.py +2 -2
  26. qiskit/transpiler/passes/scheduling/alignments/align_measures.py +3 -3
  27. qiskit/transpiler/passes/scheduling/asap.py +2 -2
  28. qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +1 -1
  29. qiskit/transpiler/passes/scheduling/padding/base_padding.py +2 -2
  30. qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +5 -5
  31. qiskit/transpiler/passes/scheduling/padding/pad_delay.py +1 -1
  32. qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py +29 -19
  33. qiskit/visualization/timeline/core.py +1 -1
  34. {qiskit-1.4.1.dist-info → qiskit-1.4.3.dist-info}/METADATA +4 -3
  35. {qiskit-1.4.1.dist-info → qiskit-1.4.3.dist-info}/RECORD +709 -708
  36. {qiskit-1.4.1.dist-info → qiskit-1.4.3.dist-info}/WHEEL +1 -1
  37. {qiskit-1.4.1.dist-info → qiskit-1.4.3.dist-info}/entry_points.txt +0 -0
  38. {qiskit-1.4.1.dist-info → qiskit-1.4.3.dist-info/licenses}/LICENSE.txt +0 -0
  39. {qiskit-1.4.1.dist-info → qiskit-1.4.3.dist-info}/top_level.txt +0 -0
qiskit/VERSION.txt CHANGED
@@ -1 +1 @@
1
- 1.4.1
1
+ 1.4.3
Binary file
@@ -65,29 +65,29 @@ def convert_durations_to_dt(qc: QuantumCircuit, dt_in_sec: float, inplace=True):
65
65
 
66
66
  for instruction in circ.data:
67
67
  operation = instruction.operation
68
- if operation.unit == "dt" or operation.duration is None:
68
+ if operation._unit == "dt" or operation._duration is None:
69
69
  continue
70
70
 
71
- if not operation.unit.endswith("s"):
72
- raise CircuitError(f"Invalid time unit: '{operation.unit}'")
71
+ if not operation._unit.endswith("s"):
72
+ raise CircuitError(f"Invalid time unit: '{operation._unit}'")
73
73
 
74
- duration = operation.duration
75
- if operation.unit != "s":
76
- duration = apply_prefix(duration, operation.unit)
74
+ duration = operation._duration
75
+ if operation._unit != "s":
76
+ duration = apply_prefix(duration, operation._unit)
77
77
 
78
- operation.duration = duration_in_dt(duration, dt_in_sec)
79
- operation.unit = "dt"
78
+ operation._duration = duration_in_dt(duration, dt_in_sec)
79
+ operation._unit = "dt"
80
80
 
81
- if circ.duration is not None and circ.unit != "dt":
82
- if not circ.unit.endswith("s"):
83
- raise CircuitError(f"Invalid time unit: '{circ.unit}'")
81
+ if circ._duration is not None and circ._unit != "dt":
82
+ if not circ._unit.endswith("s"):
83
+ raise CircuitError(f"Invalid time unit: '{circ._unit}'")
84
84
 
85
- duration = circ.duration
86
- if circ.unit != "s":
87
- duration = apply_prefix(duration, circ.unit)
85
+ duration = circ._duration
86
+ if circ._unit != "s":
87
+ duration = apply_prefix(duration, circ._unit)
88
88
 
89
- circ.duration = duration_in_dt(duration, dt_in_sec)
90
- circ.unit = "dt"
89
+ circ._duration = duration_in_dt(duration, dt_in_sec)
90
+ circ._unit = "dt"
91
91
 
92
92
  if not inplace:
93
93
  return circ
@@ -33,9 +33,10 @@ class RGate(Gate):
33
33
 
34
34
  .. code-block:: text
35
35
 
36
- ┌──────┐
37
- q_0: ┤ R(ϴ)
38
- └──────┘
36
+ ┌─────────┐
37
+ q_0: ┤ R(θ,ϕ)
38
+ └─────────┘
39
+
39
40
 
40
41
  **Matrix Representation:**
41
42
 
@@ -306,8 +306,7 @@ class CCXGate(SingletonControlledGate):
306
306
  r"""CCX gate, also known as Toffoli gate.
307
307
 
308
308
  Can be applied to a :class:`~qiskit.circuit.QuantumCircuit`
309
- with the :meth:`~qiskit.circuit.QuantumCircuit.ccx` and
310
- :meth:`~qiskit.circuit.QuantumCircuit.toffoli` methods.
309
+ with the :meth:`~qiskit.circuit.QuantumCircuit.ccx` method.
311
310
 
312
311
  **Circuit symbol:**
313
312
 
@@ -25,7 +25,7 @@ import operator
25
25
  import numpy
26
26
  import symengine
27
27
 
28
- from qiskit.circuit.exceptions import CircuitError
28
+ from qiskit.circuit.exceptions import CircuitError, QiskitError
29
29
 
30
30
  # This type is redefined at the bottom to insert the full reference to "ParameterExpression", so it
31
31
  # can safely be used by runtime type-checkers like Sphinx. Mypy does not need this because it
@@ -528,6 +528,9 @@ class ParameterExpression:
528
528
  def __str__(self):
529
529
  from sympy import sympify, sstr
530
530
 
531
+ if not isinstance(self._symbol_expr, symengine.Basic):
532
+ raise QiskitError("Invalid ParameterExpression")
533
+
531
534
  return sstr(sympify(self._symbol_expr), full_prec=False)
532
535
 
533
536
  def __complex__(self):
@@ -608,6 +611,8 @@ class ParameterExpression:
608
611
  return False
609
612
  from sympy import sympify
610
613
 
614
+ if not isinstance(self._symbol_expr, symengine.Basic):
615
+ raise QiskitError("Invalid ParameterExpression")
611
616
  return sympify(self._symbol_expr).equals(sympify(other._symbol_expr))
612
617
  elif isinstance(other, numbers.Number):
613
618
  return len(self.parameters) == 0 and complex(self._symbol_expr) == other
@@ -1546,8 +1546,8 @@ class QuantumCircuit:
1546
1546
  for instruction in reversed(self.data):
1547
1547
  reverse_circ._append(instruction.replace(operation=instruction.operation.reverse_ops()))
1548
1548
 
1549
- reverse_circ.duration = self.duration
1550
- reverse_circ.unit = self.unit
1549
+ reverse_circ._duration = self._duration
1550
+ reverse_circ._unit = self._unit
1551
1551
  return reverse_circ
1552
1552
 
1553
1553
  def reverse_bits(self) -> "QuantumCircuit":
@@ -2620,8 +2620,8 @@ class QuantumCircuit:
2620
2620
  """
2621
2621
  if _standard_gate:
2622
2622
  self._data.append(instruction)
2623
- self.duration = None
2624
- self.unit = "dt"
2623
+ self._duration = None
2624
+ self._unit = "dt"
2625
2625
  return instruction
2626
2626
 
2627
2627
  old_style = not isinstance(instruction, CircuitInstruction)
@@ -2643,8 +2643,8 @@ class QuantumCircuit:
2643
2643
  self._data.append_manual_params(instruction, params)
2644
2644
 
2645
2645
  # Invalidate whole circuit duration if an instruction is added
2646
- self.duration = None
2647
- self.unit = "dt"
2646
+ self._duration = None
2647
+ self._unit = "dt"
2648
2648
  return instruction.operation if old_style else instruction
2649
2649
 
2650
2650
  @typing.overload
@@ -6584,7 +6584,7 @@ class QuantumCircuit:
6584
6584
  Raises:
6585
6585
  CircuitError: if ``self`` is a not-yet scheduled circuit.
6586
6586
  """
6587
- if self.duration is None:
6587
+ if self._duration is None:
6588
6588
  # circuit has only delays, this is kind of scheduled
6589
6589
  for instruction in self._data:
6590
6590
  if not isinstance(instruction.operation, Delay):
@@ -6626,7 +6626,7 @@ class QuantumCircuit:
6626
6626
  Raises:
6627
6627
  CircuitError: if ``self`` is a not-yet scheduled circuit.
6628
6628
  """
6629
- if self.duration is None:
6629
+ if self._duration is None:
6630
6630
  # circuit has only delays, this is kind of scheduled
6631
6631
  for instruction in self._data:
6632
6632
  if not isinstance(instruction.operation, Delay):
@@ -6637,7 +6637,7 @@ class QuantumCircuit:
6637
6637
 
6638
6638
  qubits = [self.qubits[q] if isinstance(q, int) else q for q in qubits]
6639
6639
 
6640
- stops = {q: self.duration for q in qubits}
6640
+ stops = {q: self._duration for q in qubits}
6641
6641
  dones = {q: False for q in qubits}
6642
6642
  for instruction in reversed(self._data):
6643
6643
  for q in qubits:
@@ -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:
@@ -73,6 +73,6 @@ def circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_ord
73
73
 
74
74
  dagcircuit = core_circuit_to_dag(circuit, copy_operations, qubit_order, clbit_order)
75
75
 
76
- dagcircuit.duration = circuit._duration
77
- dagcircuit.unit = circuit._unit
76
+ dagcircuit._duration = circuit._duration
77
+ dagcircuit._unit = circuit._unit
78
78
  return dagcircuit
@@ -74,7 +74,6 @@ def dag_to_circuit(dag, copy_operations=True):
74
74
  circuit._calibrations_prop = dag._calibrations_prop
75
75
 
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
@@ -525,8 +525,6 @@ class _DAGDependencyV2:
525
525
  target_dag = _DAGDependencyV2()
526
526
  target_dag.name = self.name
527
527
  target_dag._global_phase = self._global_phase
528
- target_dag.duration = self.duration
529
- target_dag.unit = self.unit
530
528
  target_dag.metadata = self.metadata
531
529
  target_dag._key_cache = self._key_cache
532
530
  target_dag.comm_checker = self.comm_checker
@@ -534,6 +532,9 @@ class _DAGDependencyV2:
534
532
  target_dag.add_qubits(self.qubits)
535
533
  target_dag.add_clbits(self.clbits)
536
534
 
535
+ target_dag.duration = self.duration
536
+ target_dag.unit = self.unit
537
+
537
538
  for qreg in self.qregs.values():
538
539
  target_dag.add_qreg(qreg)
539
540
  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
 
@@ -638,7 +638,7 @@ def _read_custom_operations(file_obj, version, vectors):
638
638
  return custom_operations
639
639
 
640
640
 
641
- def _read_calibrations(file_obj, version, vectors, metadata_deserializer):
641
+ def _read_calibrations(file_obj, version, vectors, metadata_deserializer, trust_input=False):
642
642
  calibrations = {}
643
643
 
644
644
  header = formats.CALIBRATION._make(
@@ -656,7 +656,12 @@ def _read_calibrations(file_obj, version, vectors, metadata_deserializer):
656
656
  params = tuple(
657
657
  value.read_value(file_obj, version, vectors) for _ in range(defheader.num_params)
658
658
  )
659
- schedule = schedules.read_schedule_block(file_obj, version, metadata_deserializer)
659
+ schedule = schedules.read_schedule_block(
660
+ file_obj,
661
+ version,
662
+ metadata_deserializer,
663
+ trust_input=trust_input,
664
+ )
660
665
 
661
666
  if name not in calibrations:
662
667
  calibrations[name] = {(qubits, params): schedule}
@@ -771,6 +776,11 @@ def _write_instruction(
771
776
  custom_operations[gate_class_name] = instruction.operation
772
777
  custom_operations_list.append(gate_class_name)
773
778
 
779
+ elif isinstance(instruction.operation, library.MCMTGate):
780
+ gate_class_name = instruction.operation.name + "_" + str(uuid.uuid4())
781
+ custom_operations[gate_class_name] = instruction.operation
782
+ custom_operations_list.append(gate_class_name)
783
+
774
784
  condition_type = type_keys.Condition.NONE
775
785
  condition_register = b""
776
786
  condition_value = 0
@@ -1327,7 +1337,9 @@ def write_circuit(
1327
1337
  _write_layout(file_obj, circuit)
1328
1338
 
1329
1339
 
1330
- def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=False):
1340
+ def read_circuit(
1341
+ file_obj, version, metadata_deserializer=None, use_symengine=False, trust_input=False
1342
+ ):
1331
1343
  """Read a single QuantumCircuit object from the file like object.
1332
1344
 
1333
1345
  Args:
@@ -1345,6 +1357,7 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa
1345
1357
  supported in all platforms. Please check that your target platform is supported by
1346
1358
  the symengine library before setting this option, as it will be required by qpy to
1347
1359
  deserialize the payload.
1360
+ trust_input (bool): If true, deserialize vulnerable schedule block payloads.
1348
1361
  Returns:
1349
1362
  QuantumCircuit: The circuit object from the file.
1350
1363
 
@@ -1454,7 +1467,11 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa
1454
1467
  # Read calibrations
1455
1468
  if version >= 5:
1456
1469
  circ._calibrations_prop = _read_calibrations(
1457
- file_obj, version, vectors, metadata_deserializer
1470
+ file_obj,
1471
+ version,
1472
+ vectors,
1473
+ metadata_deserializer,
1474
+ trust_input=trust_input,
1458
1475
  )
1459
1476
 
1460
1477
  for vec_name, (vector, initialized_params) in vectors.items():
@@ -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()
@@ -99,7 +99,7 @@ def _read_discriminator(file_obj, version):
99
99
  return Discriminator(name=name, **params)
100
100
 
101
101
 
102
- def _loads_symbolic_expr(expr_bytes, use_symengine=False):
102
+ def _loads_symbolic_expr(expr_bytes, use_symengine=False, trust_input=False):
103
103
  if expr_bytes == b"":
104
104
  return None
105
105
  expr_bytes = zlib.decompress(expr_bytes)
@@ -108,12 +108,18 @@ def _loads_symbolic_expr(expr_bytes, use_symengine=False):
108
108
  else:
109
109
  from sympy import parse_expr
110
110
 
111
+ if not trust_input:
112
+ raise QpyError(
113
+ "This payload can not be loaded unless you set ``trust_payload`` to "
114
+ "True, as it's using sympy for serialization of symbolic expressions which "
115
+ "is insecure."
116
+ )
111
117
  expr_txt = expr_bytes.decode(common.ENCODE)
112
118
  expr = parse_expr(expr_txt)
113
119
  return sym.sympify(expr)
114
120
 
115
121
 
116
- def _read_symbolic_pulse(file_obj, version):
122
+ def _read_symbolic_pulse(file_obj, version, trust_input=False):
117
123
  make = formats.SYMBOLIC_PULSE._make
118
124
  pack = formats.SYMBOLIC_PULSE_PACK
119
125
  size = formats.SYMBOLIC_PULSE_SIZE
@@ -125,9 +131,13 @@ def _read_symbolic_pulse(file_obj, version):
125
131
  )
126
132
  )
127
133
  pulse_type = file_obj.read(header.type_size).decode(common.ENCODE)
128
- envelope = _loads_symbolic_expr(file_obj.read(header.envelope_size))
129
- constraints = _loads_symbolic_expr(file_obj.read(header.constraints_size))
130
- valid_amp_conditions = _loads_symbolic_expr(file_obj.read(header.valid_amp_conditions_size))
134
+ envelope = _loads_symbolic_expr(file_obj.read(header.envelope_size), trust_input=trust_input)
135
+ constraints = _loads_symbolic_expr(
136
+ file_obj.read(header.constraints_size), trust_input=trust_input
137
+ )
138
+ valid_amp_conditions = _loads_symbolic_expr(
139
+ file_obj.read(header.valid_amp_conditions_size), trust_input=trust_input
140
+ )
131
141
  parameters = common.read_mapping(
132
142
  file_obj,
133
143
  deserializer=value.loads_value,
@@ -189,7 +199,7 @@ def _read_symbolic_pulse(file_obj, version):
189
199
  raise NotImplementedError(f"Unknown class '{class_name}'")
190
200
 
191
201
 
192
- def _read_symbolic_pulse_v6(file_obj, version, use_symengine):
202
+ def _read_symbolic_pulse_v6(file_obj, version, use_symengine, trust_input=False):
193
203
  make = formats.SYMBOLIC_PULSE_V2._make
194
204
  pack = formats.SYMBOLIC_PULSE_PACK_V2
195
205
  size = formats.SYMBOLIC_PULSE_SIZE_V2
@@ -202,10 +212,14 @@ def _read_symbolic_pulse_v6(file_obj, version, use_symengine):
202
212
  )
203
213
  class_name = file_obj.read(header.class_name_size).decode(common.ENCODE)
204
214
  pulse_type = file_obj.read(header.type_size).decode(common.ENCODE)
205
- envelope = _loads_symbolic_expr(file_obj.read(header.envelope_size), use_symengine)
206
- constraints = _loads_symbolic_expr(file_obj.read(header.constraints_size), use_symengine)
215
+ envelope = _loads_symbolic_expr(
216
+ file_obj.read(header.envelope_size), use_symengine, trust_input=trust_input
217
+ )
218
+ constraints = _loads_symbolic_expr(
219
+ file_obj.read(header.constraints_size), use_symengine, trust_input=trust_input
220
+ )
207
221
  valid_amp_conditions = _loads_symbolic_expr(
208
- file_obj.read(header.valid_amp_conditions_size), use_symengine
222
+ file_obj.read(header.valid_amp_conditions_size), use_symengine, trust_input=trust_input
209
223
  )
210
224
  parameters = common.read_mapping(
211
225
  file_obj,
@@ -277,15 +291,21 @@ def _read_alignment_context(file_obj, version):
277
291
 
278
292
 
279
293
  # pylint: disable=too-many-return-statements
280
- def _loads_operand(type_key, data_bytes, version, use_symengine):
294
+ def _loads_operand(type_key, data_bytes, version, use_symengine, trust_input=False):
281
295
  if type_key == type_keys.ScheduleOperand.WAVEFORM:
282
296
  return common.data_from_binary(data_bytes, _read_waveform, version=version)
283
297
  if type_key == type_keys.ScheduleOperand.SYMBOLIC_PULSE:
284
298
  if version < 6:
285
- return common.data_from_binary(data_bytes, _read_symbolic_pulse, version=version)
299
+ return common.data_from_binary(
300
+ data_bytes, _read_symbolic_pulse, version=version, trust_input=trust_input
301
+ )
286
302
  else:
287
303
  return common.data_from_binary(
288
- data_bytes, _read_symbolic_pulse_v6, version=version, use_symengine=use_symengine
304
+ data_bytes,
305
+ _read_symbolic_pulse_v6,
306
+ version=version,
307
+ use_symengine=use_symengine,
308
+ trust_input=trust_input,
289
309
  )
290
310
  if type_key == type_keys.ScheduleOperand.CHANNEL:
291
311
  return common.data_from_binary(data_bytes, _read_channel, version=version)
@@ -307,14 +327,20 @@ def _loads_operand(type_key, data_bytes, version, use_symengine):
307
327
  return value.loads_value(type_key, data_bytes, version, {})
308
328
 
309
329
 
310
- def _read_element(file_obj, version, metadata_deserializer, use_symengine):
330
+ def _read_element(file_obj, version, metadata_deserializer, use_symengine, trust_input=False):
311
331
  type_key = common.read_type_key(file_obj)
312
332
 
313
333
  if type_key == type_keys.Program.SCHEDULE_BLOCK:
314
- return read_schedule_block(file_obj, version, metadata_deserializer, use_symengine)
334
+ return read_schedule_block(
335
+ file_obj, version, metadata_deserializer, use_symengine, trust_input=trust_input
336
+ )
315
337
 
316
338
  operands = common.read_sequence(
317
- file_obj, deserializer=_loads_operand, version=version, use_symengine=use_symengine
339
+ file_obj,
340
+ deserializer=_loads_operand,
341
+ version=version,
342
+ use_symengine=use_symengine,
343
+ trust_input=trust_input,
318
344
  )
319
345
  name = value.read_value(file_obj, version, {})
320
346
 
@@ -326,7 +352,7 @@ def _read_element(file_obj, version, metadata_deserializer, use_symengine):
326
352
  return instance
327
353
 
328
354
 
329
- def _loads_reference_item(type_key, data_bytes, metadata_deserializer, version):
355
+ def _loads_reference_item(type_key, data_bytes, metadata_deserializer, version, trust_input=False):
330
356
  if type_key == type_keys.Value.NULL:
331
357
  return None
332
358
  if type_key == type_keys.Program.SCHEDULE_BLOCK:
@@ -335,6 +361,7 @@ def _loads_reference_item(type_key, data_bytes, metadata_deserializer, version):
335
361
  deserializer=read_schedule_block,
336
362
  version=version,
337
363
  metadata_deserializer=metadata_deserializer,
364
+ trust_input=trust_input,
338
365
  )
339
366
 
340
367
  raise QpyError(
@@ -408,6 +435,9 @@ def _dumps_symbolic_expr(expr, use_symengine):
408
435
  else:
409
436
  from sympy import srepr, sympify
410
437
 
438
+ if not isinstance(expr, sym.Basic):
439
+ raise QiskitError("Invalid ParameterExpression")
440
+
411
441
  expr_bytes = srepr(sympify(expr)).encode(common.ENCODE)
412
442
  return zlib.compress(expr_bytes)
413
443
 
@@ -512,7 +542,9 @@ def _dumps_reference_item(schedule, metadata_serializer, version):
512
542
 
513
543
 
514
544
  @ignore_pulse_deprecation_warnings
515
- def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symengine=False):
545
+ def read_schedule_block(
546
+ file_obj, version, metadata_deserializer=None, use_symengine=False, trust_input=False
547
+ ):
516
548
  """Read a single ScheduleBlock from the file like object.
517
549
 
518
550
  Args:
@@ -529,6 +561,14 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symen
529
561
  native mechanism. This is a faster serialization alternative, but not supported in all
530
562
  platforms. Please check that your target platform is supported by the symengine library
531
563
  before setting this option, as it will be required by qpy to deserialize the payload.
564
+ trust_input (bool): if set to ``False`` (the default),
565
+ :class:`.ScheduleBlock` objects in the payload that were
566
+ serialized using ``sympy`` are not allowed and will error. This
567
+ is because the ``sympy`` parsing uses :func:`eval`, which
568
+ can allow for arbitrary code execution.
569
+ The flag should only be set
570
+ to ``True`` if you trust the QPY payload you are loading.
571
+
532
572
  Returns:
533
573
  ScheduleBlock: The schedule block object from the file.
534
574
 
@@ -556,7 +596,9 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symen
556
596
  alignment_context=context,
557
597
  )
558
598
  for _ in range(data.num_elements):
559
- block_elm = _read_element(file_obj, version, metadata_deserializer, use_symengine)
599
+ block_elm = _read_element(
600
+ file_obj, version, metadata_deserializer, use_symengine, trust_input=trust_input
601
+ )
560
602
  block.append(block_elm, inplace=True)
561
603
 
562
604
  # Load references
@@ -566,6 +608,7 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symen
566
608
  deserializer=_loads_reference_item,
567
609
  version=version,
568
610
  metadata_deserializer=metadata_deserializer,
611
+ trust_input=trust_input,
569
612
  )
570
613
  ref_dict = {}
571
614
  for key_str, schedule in flat_key_refdict.items():
@@ -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):
@@ -171,6 +172,9 @@ def _write_parameter_expression(file_obj, obj, use_symengine, *, version):
171
172
  else:
172
173
  from sympy import srepr, sympify
173
174
 
175
+ if not isinstance(obj._symbol_expr, symengine.Basic):
176
+ raise exceptions.QpyError("Invalid ParameterExpression")
177
+
174
178
  expr_bytes = srepr(sympify(obj._symbol_expr)).encode(common.ENCODE)
175
179
  else:
176
180
  with io.BytesIO() as buf:
@@ -419,9 +423,9 @@ def _read_parameter_expression(file_obj):
419
423
  data = formats.PARAMETER_EXPR(
420
424
  *struct.unpack(formats.PARAMETER_EXPR_PACK, file_obj.read(formats.PARAMETER_EXPR_SIZE))
421
425
  )
422
- from sympy.parsing.sympy_parser import parse_expr
423
426
 
424
- expr_ = symengine.sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE)))
427
+ sympy_str = file_obj.read(data.expr_size).decode(common.ENCODE)
428
+ expr_ = symengine.sympify(parse_sympy_repr(sympy_str))
425
429
  symbol_map = {}
426
430
  for _ in range(data.map_elements):
427
431
  elem_data = formats.PARAM_EXPR_MAP_ELEM(
@@ -460,9 +464,8 @@ def _read_parameter_expression_v3(file_obj, vectors, use_symengine):
460
464
  if use_symengine:
461
465
  expr_ = common.load_symengine_payload(payload)
462
466
  else:
463
- from sympy.parsing.sympy_parser import parse_expr
464
-
465
- expr_ = symengine.sympify(parse_expr(payload.decode(common.ENCODE)))
467
+ sympy_str = payload.decode(common.ENCODE)
468
+ expr_ = symengine.sympify(parse_sympy_repr(sympy_str))
466
469
 
467
470
  symbol_map = {}
468
471
  for _ in range(data.map_elements):