pyrauli 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {pyrauli-0.3.0 → pyrauli-0.3.2}/PKG-INFO +3 -2
  2. {pyrauli-0.3.0 → pyrauli-0.3.2}/README.md +1 -1
  3. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/guides/how_to_qiskit.rst +14 -0
  4. {pyrauli-0.3.0 → pyrauli-0.3.2}/pyproject.toml +3 -2
  5. {pyrauli-0.3.0 → pyrauli-0.3.2}/src/pyrauli/__init__.py +2 -2
  6. {pyrauli-0.3.0 → pyrauli-0.3.2}/src/pyrauli/backend.py +2 -2
  7. {pyrauli-0.3.0 → pyrauli-0.3.2}/src/pyrauli/estimator.py +96 -32
  8. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/test_backend.py +48 -2
  9. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/test_truncator.py +14 -0
  10. {pyrauli-0.3.0 → pyrauli-0.3.2}/.clang-format +0 -0
  11. {pyrauli-0.3.0 → pyrauli-0.3.2}/.github/workflows/benchmark.yml +0 -0
  12. {pyrauli-0.3.0 → pyrauli-0.3.2}/.github/workflows/ci.yml +0 -0
  13. {pyrauli-0.3.0 → pyrauli-0.3.2}/.github/workflows/doc.yml +0 -0
  14. {pyrauli-0.3.0 → pyrauli-0.3.2}/.github/workflows/pypi.yml +0 -0
  15. {pyrauli-0.3.0 → pyrauli-0.3.2}/.gitignore +0 -0
  16. {pyrauli-0.3.0 → pyrauli-0.3.2}/CMakeLists.txt +0 -0
  17. {pyrauli-0.3.0 → pyrauli-0.3.2}/LICENSE +0 -0
  18. {pyrauli-0.3.0 → pyrauli-0.3.2}/benchmarks/test_benchmark_circuit.py +0 -0
  19. {pyrauli-0.3.0 → pyrauli-0.3.2}/benchmarks/test_benchmark_observable.py +0 -0
  20. {pyrauli-0.3.0 → pyrauli-0.3.2}/benchmarks/test_benchmark_qiskit.py +0 -0
  21. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/.gitignore +0 -0
  22. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/Makefile +0 -0
  23. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/_static/.gitkeep +0 -0
  24. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/_templates/.gitkeep +0 -0
  25. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/conf.py +0 -0
  26. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/explanation/theory.rst +0 -0
  27. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/guides/how_to_circuit.rst +0 -0
  28. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/guides/how_to_complexity.rst +0 -0
  29. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/guides/how_to_noise.rst +0 -0
  30. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/guides/how_to_observables.rst +0 -0
  31. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/index.rst +0 -0
  32. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/make.bat +0 -0
  33. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/reference/api.rst +0 -0
  34. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/requirements.txt +0 -0
  35. {pyrauli-0.3.0 → pyrauli-0.3.2}/docs/tutorials/getting_started.rst +0 -0
  36. {pyrauli-0.3.0 → pyrauli-0.3.2}/examples/qiskit_backend.py +0 -0
  37. {pyrauli-0.3.0 → pyrauli-0.3.2}/src/pyrauli/_core/bindings.cpp +0 -0
  38. {pyrauli-0.3.0 → pyrauli-0.3.2}/src/pyrauli/converters.py +0 -0
  39. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/snippets/test_basic_circuit.py +0 -0
  40. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/snippets/test_observable_evolution.py +0 -0
  41. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/snippets/test_qiskit_backend_usage.py +0 -0
  42. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/snippets/test_readme.py +0 -0
  43. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/test_circuit.py +0 -0
  44. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/test_circuit_qiskit.py +0 -0
  45. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/test_noise_model.py +0 -0
  46. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/test_observable.py +0 -0
  47. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/test_observable_qiskit.py +0 -0
  48. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/test_pauli.py +0 -0
  49. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/test_policies.py +0 -0
  50. {pyrauli-0.3.0 → pyrauli-0.3.2}/tests/test_pyrauli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pyrauli
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: A very fast and easy to use Quantum circuit simulator relying on Pauli propagation. Compatible with qiskit.
5
5
  License: GNU GENERAL PUBLIC LICENSE
6
6
  Version 3, 29 June 2007
@@ -686,6 +686,7 @@ Project-URL: Homepage, https://github.com/zefresk/pyrauli
686
686
  Requires-Python: >=3.9
687
687
  Provides-Extra: qiskit
688
688
  Requires-Dist: qiskit>=2.0.0; extra == "qiskit"
689
+ Requires-Dist: numpy; extra == "qiskit"
689
690
  Provides-Extra: test
690
691
  Requires-Dist: pytest>=8.0.0; extra == "test"
691
692
  Requires-Dist: pytest-cov>=5.0.0; extra == "test"
@@ -753,7 +754,7 @@ final_observable = circuit.run(observable)
753
754
 
754
755
  # 5. Retrieve the final expectation value
755
756
  # The expectation value is calculated with respect to the initial |00...0> state
756
- expectation_value = final_observable.get_expectation_value()
757
+ expectation_value = final_observable.expectation_value()
757
758
 
758
759
  print(f"Final observable: {final_observable}")
759
760
  print(f"Expectation value: {expectation_value}")
@@ -58,7 +58,7 @@ final_observable = circuit.run(observable)
58
58
 
59
59
  # 5. Retrieve the final expectation value
60
60
  # The expectation value is calculated with respect to the initial |00...0> state
61
- expectation_value = final_observable.get_expectation_value()
61
+ expectation_value = final_observable.expectation_value()
62
62
 
63
63
  print(f"Final observable: {final_observable}")
64
64
  print(f"Expectation value: {expectation_value}")
@@ -60,6 +60,20 @@ the hood.
60
60
  :end-before: # [estimator_complex]
61
61
  :dedent: 4
62
62
 
63
+ Full PUBs support
64
+ -----------------
65
+
66
+ ``pyrauli`` intends to be fully compatible with Qiskit and the provided backends and estimators should be able to be swapped with any other qiskit backend. Therefore, :py:class:`~pyrauli.PyrauliEstimator` and :py:class:`~pyrauli.PBackend` `.run` methods allows for any PUB (see `Qiskit documentation on PUBs https://qiskit.qotlabs.org/docs/guides/primitive-input-output`).
67
+
68
+ Below is an example of what is possible:
69
+
70
+ .. literalinclude:: /../tests/test_backend.py
71
+ :language: python
72
+ :start-after: # [qiskit_multiparameters]
73
+ :end-before: # [qiskit_multiparameters]
74
+ :dedent: 4
75
+
76
+
63
77
  Qiskit and reverse qubit ordering
64
78
  ---------------------------------
65
79
 
@@ -5,7 +5,7 @@ build-backend = "scikit_build_core.build"
5
5
 
6
6
  [project]
7
7
  name = "pyrauli"
8
- version = "0.3.0"
8
+ version = "0.3.2"
9
9
  description = "A very fast and easy to use Quantum circuit simulator relying on Pauli propagation. Compatible with qiskit."
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.9"
@@ -25,7 +25,8 @@ Homepage = "https://github.com/zefresk/pyrauli"
25
25
 
26
26
  [project.optional-dependencies]
27
27
  qiskit = [
28
- "qiskit>=2.0.0"
28
+ "qiskit>=2.0.0",
29
+ "numpy"
29
30
  ]
30
31
  test = [
31
32
  "pytest>=8.0.0",
@@ -8,7 +8,7 @@ simulation, powered by the C++ `propauli` library.
8
8
  from ._core import (
9
9
  PauliEnum, PauliGate, CliffordGate, UnitalNoise, QGate, Pauli, PauliTerm,
10
10
  Observable, Noise, NoiseModel, Truncator, CoefficientTruncator,
11
- WeightTruncator, NeverTruncator, LambdaTruncator, MultiTruncator,
11
+ WeightTruncator, KeepNTruncator, NeverTruncator, LambdaTruncator, MultiTruncator,
12
12
  SchedulingPolicy, NeverPolicy, AlwaysBeforeSplittingPolicy,
13
13
  AlwaysAfterSplittingPolicy, Circuit, OperationType, Timing,
14
14
  SimulationState, CompressionResult, LambdaPolicy,
@@ -17,7 +17,7 @@ from ._core import (
17
17
  __all__ = [
18
18
  "PauliEnum", "PauliGate", "CliffordGate", "UnitalNoise", "QGate", "Pauli",
19
19
  "PauliTerm", "Observable", "Noise", "NoiseModel", "Truncator",
20
- "CoefficientTruncator", "WeightTruncator", "NeverTruncator",
20
+ "CoefficientTruncator", "WeightTruncator", "KeepNTruncator", "NeverTruncator",
21
21
  "LambdaTruncator", "MultiTruncator", "SchedulingPolicy", "NeverPolicy",
22
22
  "AlwaysBeforeSplittingPolicy", "AlwaysAfterSplittingPolicy", "Circuit",
23
23
  "OperationType", "Timing", "SimulationState", "CompressionResult",
@@ -93,14 +93,14 @@ class PBackend(BackendV2):
93
93
  """Default options for the backend."""
94
94
  return Options()
95
95
 
96
- def run(self, run_input: Union[QuantumCircuit, List[QuantumCircuit]], **options) -> JobV1:
96
+ def run(self, run_input: Union[List[Tuple], Tuple], **options) -> JobV1:
97
97
  """
98
98
  Run a circuit or a list of circuits on the backend.
99
99
 
100
100
  This backend uses a PyrauliEstimator to execute the circuits.
101
101
 
102
102
  Args:
103
- run_input: A QuantumCircuit or a list of them to run.
103
+ run_input: A list of pubs, where each pub is a tuple of (circuit, observables, parameter_values) or any other PUBs structure.
104
104
  **options: Runtime options for the execution. may include: "noise_model", "truncator", "merge_policy", "truncate_policy"
105
105
 
106
106
  Returns:
@@ -7,15 +7,17 @@ managing simulation jobs.
7
7
  """
8
8
  import uuid
9
9
  import numpy as np
10
- from typing import List, Tuple, Union
10
+ from typing import List, Tuple, Union, Optional, Any, Callable, Dict
11
11
  from concurrent.futures import ThreadPoolExecutor
12
12
 
13
+ from qiskit import QuantumCircuit
13
14
  from qiskit.primitives import BaseEstimatorV2
14
15
  from qiskit.providers import BackendV2, JobV1, Options, JobStatus
15
16
  from qiskit.result import Result
16
17
  from qiskit.primitives.primitive_job import PrimitiveJob
17
18
  from qiskit.primitives.containers import PubResult, PrimitiveResult, DataBin
18
19
  from qiskit.quantum_info import SparsePauliOp
20
+ from qiskit.circuit import ParameterVector
19
21
 
20
22
  from . import Circuit, Observable, NoiseModel, Truncator, NeverTruncator, SchedulingPolicy, AlwaysAfterSplittingPolicy
21
23
  from .converters import from_qiskit
@@ -43,7 +45,11 @@ class PyrauliEstimator(BaseEstimatorV2):
43
45
  result = job.result()
44
46
  print(result[0].data.evs[0])
45
47
  """
46
- def __init__(self, noise_model: NoiseModel = None, truncator: Truncator = NeverTruncator(), merge_policy: SchedulingPolicy = AlwaysAfterSplittingPolicy(), truncate_policy: SchedulingPolicy = AlwaysAfterSplittingPolicy()):
48
+ def __init__(self,
49
+ noise_model: Optional[NoiseModel] = None,
50
+ truncator: Truncator = NeverTruncator(),
51
+ merge_policy: SchedulingPolicy = AlwaysAfterSplittingPolicy(),
52
+ truncate_policy: SchedulingPolicy = AlwaysAfterSplittingPolicy()) -> None:
47
53
  """
48
54
  Initializes the PyrauliEstimator.
49
55
 
@@ -59,18 +65,16 @@ class PyrauliEstimator(BaseEstimatorV2):
59
65
  self._truncator = truncator or NeverTruncator()
60
66
  self._merge_policy = merge_policy or AlwaysAfterSplittingPolicy()
61
67
  self._truncate_policy = truncate_policy or AlwaysAfterSplittingPolicy()
62
-
63
68
  self._executor = ThreadPoolExecutor(max_workers=1)
64
69
 
65
-
66
- def run(self, run_input: List[Tuple], **options) -> 'PJob':
70
+ def run(self, run_input: Union[List[Tuple], Tuple], **options: Any) -> 'PJob':
67
71
  """
68
72
  Run a list of pubs (Primitive Unified Blocs) on the estimator.
69
73
 
70
74
  Args:
71
- run_input: A list of pubs, where each pub is a tuple of
72
- (circuit, observables, parameter_values).
73
- **options: Additional circuit options: "noise_model", "truncator", "truncate_policy", "merge_policy".
75
+ run_input: A list of pubs, where each pub is a tuple of (circuit, observables, parameter_values). See qiskit documentation on PUBs
76
+ **options: Additional circuit options: "noise_model", "truncator",
77
+ "truncate_policy", "merge_policy".
74
78
 
75
79
  Returns:
76
80
  A PJob object that represents the asynchronous execution.
@@ -80,13 +84,14 @@ class PyrauliEstimator(BaseEstimatorV2):
80
84
  job.submit()
81
85
  return job
82
86
 
83
- def _run_job(self, job_id: str, pubs: List[Tuple], **options) -> Result:
87
+ def _run_job(self, job_id: str, pubs: List[Tuple], **options: Any) -> Result:
84
88
  """
85
89
  The core simulation logic that is executed by the PJob.
86
90
 
87
91
  Args:
88
92
  job_id: The ID for the job.
89
93
  pubs: The list of pubs to execute.
94
+ **options: Runtime options passed to the run method.
90
95
 
91
96
  Returns:
92
97
  A Qiskit Result object containing the simulation results.
@@ -95,45 +100,78 @@ class PyrauliEstimator(BaseEstimatorV2):
95
100
  trunc = self._truncator if "truncator" not in options else options.get("truncator")
96
101
  trunc_pol = self._truncate_policy if "truncate_policy" not in options else options.get("truncate_policy")
97
102
  merge_pol = self._merge_policy if "merge_policy" not in options else options.get("merge_policy")
98
- print(nm, self._noise_model)
99
103
 
100
104
  results = []
101
105
  for pub in pubs:
102
106
  circuit, observables, parameter_values = self._unpack_pub(pub)
103
107
 
104
108
  # Bind parameters if they exist
105
- bound_circuit = circuit.assign_parameters(parameter_values) if parameter_values is not None else circuit
109
+ bound_circuits = self._assign_parameters(circuit, parameter_values)
106
110
 
107
111
  # Convert to pyrauli objects and simulate
108
- pyrauli_circuit = from_qiskit(bound_circuit, noise_model=nm)
109
- pyrauli_circuit.set_truncator(trunc)
110
- pyrauli_circuit.set_merge_policy(merge_pol)
111
- pyrauli_circuit.set_truncate_policy(trunc_pol)
112
- exp_values = self._simulate_observables(pyrauli_circuit, observables)
112
+ pyrauli_circuits = [from_qiskit(bqc, noise_model=nm) for bqc in bound_circuits]
113
+ for pyrauli_circuit in pyrauli_circuits:
114
+ pyrauli_circuit.set_truncator(trunc)
115
+ pyrauli_circuit.set_merge_policy(merge_pol)
116
+ pyrauli_circuit.set_truncate_policy(trunc_pol)
117
+
118
+ exp_values = self._simulate_observables(pyrauli_circuits, observables, trunc, merge_pol, trunc_pol)
113
119
 
114
120
  # Format the result for this pub
115
- results.append({"success": True, "data": {"evs": exp_values}, "metadata": {}, 'shots': 1})
121
+ results.append({"success": True, "data": {"evs": np.atleast_1d(np.squeeze(np.array(exp_values)))}, "metadata": {}, 'shots': 1})
116
122
 
117
123
  return Result.from_dict({"job_id": job_id, "results": results, "success": True, "backend_name": self.name})
118
124
 
119
- def _simulate_observables(self, pyrauli_circuit: Circuit, observables: List[SparsePauliOp]) -> List[float]:
125
+ def _simulate_observables(self,
126
+ pyrauli_circuits: List[Circuit],
127
+ observables: List[SparsePauliOp],
128
+ trunc: Optional[Truncator] = None,
129
+ merge_pol: Optional[SchedulingPolicy] = None,
130
+ trunc_pol: Optional[SchedulingPolicy] = None) -> List[List[float]]:
120
131
  """
121
132
  Simulates a list of observables for a given pyrauli circuit.
122
133
  """
123
- exp_values = []
124
- for obs in observables:
134
+ ret = []
135
+ for olist in observables:
136
+ exp_values = []
137
+ for obs in olist:
125
138
  pyrauli_obs = from_qiskit(obs, reverse=True)
126
- final_observable = pyrauli_circuit.run(pyrauli_obs)
127
- exp_values.append(final_observable.expectation_value())
128
- return exp_values
139
+ for pqc in pyrauli_circuits:
140
+ final_observable = pqc.run(pyrauli_obs)
141
+ exp_values.append(final_observable.expectation_value())
142
+ ret += [exp_values]
143
+ return ret
129
144
 
130
- def _unpack_pub(self, pub: Tuple) -> Tuple:
145
+ def _unpack_pub(self, pub: Tuple) -> Tuple[QuantumCircuit, List[SparsePauliOp], Optional[Any]]:
131
146
  """
132
147
  Unpacks a pub into its components, ensuring observables are always a list.
133
148
  """
134
149
  circuit, observables, params = (pub[0], pub[1], pub[2] if len(pub) > 2 else None)
135
150
  return circuit, observables if isinstance(observables, list) else [observables], params
136
151
 
152
+ def _assign_parameters(self, circuit: QuantumCircuit, params: Optional[Any]) -> List[QuantumCircuit]:
153
+ """
154
+ Assigns parameter values to a circuit.
155
+
156
+ This method handles different shapes for the parameter values, including
157
+ no parameters, a single set of parameters, or multiple sets for
158
+ parameter sweeping.
159
+
160
+ Args:
161
+ circuit: The circuit with unbound parameters.
162
+ params: The parameter values to bind. Can be a 1D array for a single
163
+ binding or a 2D array for multiple bindings.
164
+
165
+ Returns:
166
+ A list of circuits with bound parameters.
167
+ """
168
+ if params is None:
169
+ return [circuit]
170
+ elif (hasattr(params, 'ndim') and params.ndim == 2) or ((isinstance(params, list) or hasattr(params, 'ndim')) and len(params) > 0 and isinstance(params[0], (list, tuple, dict, ParameterVector))):
171
+ return [circuit.assign_parameters(e) for e in params]
172
+ else:
173
+ return [circuit.assign_parameters(params)]
174
+
137
175
  class PJob(JobV1):
138
176
  """
139
177
  A JobV1-compliant class for handling jobs from PBackend and PyrauliEstimator.
@@ -141,7 +179,12 @@ class PJob(JobV1):
141
179
  This class wraps the execution of the simulation in a manner consistent
142
180
  with Qiskit's job management system.
143
181
  """
144
- def __init__(self, backend, job_id, fn, pubs, **options):
182
+ def __init__(self,
183
+ backend: BackendV2,
184
+ job_id: str,
185
+ fn: Callable[..., Result],
186
+ pubs: List[Tuple],
187
+ **options: Any) -> None:
145
188
  """
146
189
  Initializes the PJob.
147
190
 
@@ -155,11 +198,11 @@ class PJob(JobV1):
155
198
  super().__init__(backend, job_id)
156
199
  self._fn = fn
157
200
  self._pubs = pubs
158
- self._result = None
201
+ self._result: Optional[Result] = None
159
202
  self._status = JobStatus.INITIALIZING
160
203
  self._options = options
161
204
 
162
- def submit(self):
205
+ def submit(self) -> None:
163
206
  """
164
207
  Submit the job for execution.
165
208
 
@@ -173,7 +216,7 @@ class PJob(JobV1):
173
216
  self._status = JobStatus.ERROR
174
217
  raise e
175
218
 
176
- def result(self) -> Result:
219
+ def result(self) -> Optional[List[Result]]:
177
220
  """Return the result of the job."""
178
221
  return self._result.results if self._result is not None else None
179
222
 
@@ -182,17 +225,38 @@ class PJob(JobV1):
182
225
  return self._status
183
226
 
184
227
  def running(self) -> bool:
185
- """Return whether the job is actively running."""
228
+ """
229
+ Return whether the job is actively running.
230
+
231
+ Returns:
232
+ True if the job's status is 'RUNNING', False otherwise.
233
+ """
186
234
  return self._status == JobStatus.RUNNING
187
235
 
188
236
  def cancelled(self) -> bool:
189
- """Return whether the job has been cancelled."""
237
+ """
238
+ Return whether the job has been cancelled.
239
+
240
+ Returns:
241
+ True if the job's status is 'CANCELLED', False otherwise.
242
+ """
190
243
  return self._status == JobStatus.CANCELLED
191
244
 
192
245
  def done(self) -> bool:
193
- """Return whether the job has successfully run."""
246
+ """
247
+ Return whether the job has successfully run.
248
+
249
+ Returns:
250
+ True if the job's status is 'DONE', False otherwise.
251
+ """
194
252
  return self._status == JobStatus.DONE
195
253
 
196
254
  def in_final_state(self) -> bool:
197
- """Return whether the job is in a final job state."""
255
+ """
256
+ Return whether the job is in a final job state.
257
+
258
+ Returns:
259
+ True if the job is in a final state (DONE, ERROR, or
260
+ CANCELLED), False otherwise.
261
+ """
198
262
  return self._status in {JobStatus.DONE, JobStatus.ERROR, JobStatus.CANCELLED}
@@ -3,7 +3,7 @@ import pytest
3
3
  import numpy as np
4
4
 
5
5
  # Conditional imports for Qiskit and Aer
6
- qiskit = pytest.importorskip("qiskit", minversion="1.0", reason="Qiskit >= 1.0 is required")
6
+ qiskit = pytest.importorskip("qiskit", minversion="2.0", reason="Qiskit >= 2.0 is required")
7
7
 
8
8
  from qiskit.circuit import QuantumCircuit, Parameter, ParameterVector
9
9
  from qiskit.circuit.random import random_circuit
@@ -68,7 +68,11 @@ def test_estimator_multiple_observables_per_pub():
68
68
 
69
69
  job = estimator.run([(qc, observables)])
70
70
  result = job.result()
71
-
71
+
72
+ jobq = StatevectorEstimator().run([(qc, observables)])
73
+ resultq = jobq.result()[0]
74
+ assert resultq.data.evs.shape == result[0].data.evs.shape
75
+
72
76
  expected_evs = [p1_to_ev(0.5), p1_to_ev(0.25)]
73
77
  assert result[0].data.evs == pytest.approx(expected_evs, abs=1e-6)
74
78
 
@@ -92,6 +96,48 @@ def test_estimator_multiobs_multiparams():
92
96
  assert result[0].data.evs == pytest.approx([p1_to_ev(0.5), p1_to_ev(0.25)], abs=1e-6)
93
97
  assert result[1].data.evs == pytest.approx([p1_to_ev(0.25), p1_to_ev(0.5)], abs=1e-6)
94
98
 
99
+ def test_backend_transpile_batch_params_from_doc():
100
+ """Tests a single circuit against a list of observables in one PUB."""
101
+ # [qiskit_multiparameters]
102
+ backend = PBackend(num_qubits=2)
103
+ pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
104
+
105
+ circuit = QuantumCircuit(2)
106
+ circuit.h(0)
107
+ circuit.cx(0, 1)
108
+ circuit.ry(Parameter("a"), 0)
109
+ circuit.rz(Parameter("b"), 0)
110
+ circuit.cx(0, 1)
111
+ circuit.h(0)
112
+
113
+ transpiled_circuit = pm.run(circuit)
114
+
115
+ params = np.vstack(
116
+ [
117
+ np.linspace(-np.pi, np.pi, 100),
118
+ np.linspace(-4 * np.pi, 4 * np.pi, 100),
119
+ ]
120
+ ).T
121
+
122
+ observables = [
123
+ [SparsePauliOp(["XX", "IY"], [0.5, 0.5])],
124
+ [SparsePauliOp("XX")],
125
+ [SparsePauliOp("IY")],
126
+ ]
127
+
128
+ estimator_pub = (transpiled_circuit, observables, params)
129
+
130
+ job = backend.run([estimator_pub])
131
+ result = job.result()
132
+ # [qiskit_multiparameters]
133
+
134
+ jobq = StatevectorEstimator().run([estimator_pub])
135
+ resultq = jobq.result()[0]
136
+ assert resultq.data.evs.shape == result[0].data.evs.shape
137
+
138
+
139
+ assert result[0].data.evs == pytest.approx(resultq.data.evs, abs=1e-4)
140
+
95
141
  def test_noise_model_is_correctly_applied():
96
142
  """Confirms the noise model passed to the estimator alters the result."""
97
143
  p = 0.1
@@ -6,6 +6,7 @@ from pyrauli import (
6
6
  Circuit,
7
7
  CoefficientTruncator,
8
8
  WeightTruncator,
9
+ KeepNTruncator,
9
10
  LambdaTruncator,
10
11
  MultiTruncator,
11
12
  AlwaysAfterSplittingPolicy
@@ -37,6 +38,19 @@ def test_weight_truncator_on_observable():
37
38
  assert len(obs) == 1
38
39
  assert obs[0] == PauliTerm("IIII", 0.5)
39
40
 
41
+ def test_keepn_truncator_on_observable():
42
+ """Tests that terms with high Pauli weight are removed."""
43
+ obs = Observable([PauliTerm("IIII", 0.5), PauliTerm("ZYXI", 0.11), PauliTerm("YYYY", -0.5), PauliTerm("IXYZ", 0.1)])
44
+
45
+ truncator = KeepNTruncator(2)
46
+
47
+ removed_count = obs.truncate(truncator)
48
+
49
+ assert removed_count == 2
50
+ assert len(obs) == 2
51
+ assert obs[0] == PauliTerm("IIII", 0.5)
52
+ assert obs[1] == PauliTerm("YYYY", -0.5)
53
+
40
54
  def test_lambda_truncator_on_observable_def():
41
55
  """Tests truncation based on a custom Python function."""
42
56
  # This predicate removes any term containing a 'Y'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes