pyrauli 0.2.1__tar.gz → 0.3.0__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.2.1 → pyrauli-0.3.0}/CMakeLists.txt +1 -1
  2. {pyrauli-0.2.1 → pyrauli-0.3.0}/PKG-INFO +1 -1
  3. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/guides/how_to_circuit.rst +6 -0
  4. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/guides/how_to_complexity.rst +15 -4
  5. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/guides/how_to_qiskit.rst +13 -0
  6. {pyrauli-0.2.1 → pyrauli-0.3.0}/pyproject.toml +1 -1
  7. {pyrauli-0.2.1 → pyrauli-0.3.0}/src/pyrauli/_core/bindings.cpp +5 -0
  8. {pyrauli-0.2.1 → pyrauli-0.3.0}/src/pyrauli/converters.py +9 -4
  9. {pyrauli-0.2.1 → pyrauli-0.3.0}/src/pyrauli/estimator.py +1 -1
  10. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/test_backend.py +28 -2
  11. {pyrauli-0.2.1 → pyrauli-0.3.0}/.clang-format +0 -0
  12. {pyrauli-0.2.1 → pyrauli-0.3.0}/.github/workflows/benchmark.yml +0 -0
  13. {pyrauli-0.2.1 → pyrauli-0.3.0}/.github/workflows/ci.yml +0 -0
  14. {pyrauli-0.2.1 → pyrauli-0.3.0}/.github/workflows/doc.yml +0 -0
  15. {pyrauli-0.2.1 → pyrauli-0.3.0}/.github/workflows/pypi.yml +0 -0
  16. {pyrauli-0.2.1 → pyrauli-0.3.0}/.gitignore +0 -0
  17. {pyrauli-0.2.1 → pyrauli-0.3.0}/LICENSE +0 -0
  18. {pyrauli-0.2.1 → pyrauli-0.3.0}/README.md +0 -0
  19. {pyrauli-0.2.1 → pyrauli-0.3.0}/benchmarks/test_benchmark_circuit.py +0 -0
  20. {pyrauli-0.2.1 → pyrauli-0.3.0}/benchmarks/test_benchmark_observable.py +0 -0
  21. {pyrauli-0.2.1 → pyrauli-0.3.0}/benchmarks/test_benchmark_qiskit.py +0 -0
  22. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/.gitignore +0 -0
  23. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/Makefile +0 -0
  24. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/_static/.gitkeep +0 -0
  25. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/_templates/.gitkeep +0 -0
  26. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/conf.py +0 -0
  27. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/explanation/theory.rst +0 -0
  28. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/guides/how_to_noise.rst +0 -0
  29. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/guides/how_to_observables.rst +0 -0
  30. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/index.rst +0 -0
  31. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/make.bat +0 -0
  32. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/reference/api.rst +0 -0
  33. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/requirements.txt +0 -0
  34. {pyrauli-0.2.1 → pyrauli-0.3.0}/docs/tutorials/getting_started.rst +0 -0
  35. {pyrauli-0.2.1 → pyrauli-0.3.0}/examples/qiskit_backend.py +0 -0
  36. {pyrauli-0.2.1 → pyrauli-0.3.0}/src/pyrauli/__init__.py +0 -0
  37. {pyrauli-0.2.1 → pyrauli-0.3.0}/src/pyrauli/backend.py +0 -0
  38. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/snippets/test_basic_circuit.py +0 -0
  39. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/snippets/test_observable_evolution.py +0 -0
  40. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/snippets/test_qiskit_backend_usage.py +0 -0
  41. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/snippets/test_readme.py +0 -0
  42. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/test_circuit.py +0 -0
  43. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/test_circuit_qiskit.py +0 -0
  44. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/test_noise_model.py +0 -0
  45. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/test_observable.py +0 -0
  46. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/test_observable_qiskit.py +0 -0
  47. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/test_pauli.py +0 -0
  48. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/test_policies.py +0 -0
  49. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/test_pyrauli.py +0 -0
  50. {pyrauli-0.2.1 → pyrauli-0.3.0}/tests/test_truncator.py +0 -0
@@ -15,7 +15,7 @@ FetchContent_Declare(
15
15
  FetchContent_Declare(
16
16
  propauli
17
17
  GIT_REPOSITORY https://github.com/zefresk/propauli.git
18
- GIT_TAG v2.0.2
18
+ GIT_TAG v2.1.0
19
19
  )
20
20
 
21
21
  FetchContent_MakeAvailable(pybind11 propauli)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pyrauli
3
- Version: 0.2.1
3
+ Version: 0.3.0
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
@@ -19,3 +19,9 @@ number of qubits and then add gates sequentially. Finally, you run the circuit o
19
19
  :dedent: 4
20
20
 
21
21
 
22
+ .. NOTE::
23
+ :py:class:`~pyrauli.Circuit` supports all Pauli gates (I, X, Y, Z), the Hadamard gate (H), the CNOT gate (CX) and the RZ(:math:`\theta`) gate.
24
+
25
+
26
+ .. TIP::
27
+ For more complex gates, you may use the Qiskit transpiler on :py:class:`~Pbackend`. See :doc:`../guides/how_to_qiskit`.
@@ -12,7 +12,7 @@ manage this complexity automatically during a :py:class:`~pyrauli.Circuit.run()`
12
12
  Remove small coefficients
13
13
  -------------------------
14
14
 
15
- **Solution:** Use a :py:class:`~pyrauli.CoefficientTruncator`.
15
+ Use a :py:class:`~pyrauli.CoefficientTruncator`.
16
16
 
17
17
  This truncator removes any Pauli terms whose coefficient magnitude is below a
18
18
  given threshold.
@@ -23,10 +23,21 @@ given threshold.
23
23
  :end-before: # [truncator_coeff]
24
24
  :dedent: 4
25
25
 
26
+ Available truncators
27
+ --------------------
28
+
29
+ * :py:class:`~pyrauli.NeverTruncator`: never truncate observable.
30
+ * :py:class:`~pyrauli.CoefficientTruncator`: truncate terms with absolute coefficient value below set threshold.
31
+ * :py:class:`~pyrauli.WeightTruncator`: truncate terms with Pauli weight above set threshold.
32
+ * :py:class:`~pyrauli.LambdaTruncator`: truncate terms if they match a predicate (Python function or lambda).
33
+ * :py:class:`~pyrauli.KeepNTruncator`: truncate least significant terms if their number exceed a threshold. (Keep at most N different terms)
34
+ * :py:class:`~pyrauli.MultiTruncator`: Combine a list of truncators into one.
35
+
36
+
26
37
  Using a custom Truncator with your own logic
27
38
  --------------------------------------------
28
39
 
29
- **Solution:** Use a :py:class:`~pyrauli.LambdaTruncator`.
40
+ Use a :py:class:`~pyrauli.LambdaTruncator`.
30
41
 
31
42
  This allows you to provide a custom Python function that filters the terms.
32
43
 
@@ -37,12 +48,12 @@ This allows you to provide a custom Python function that filters the terms.
37
48
  :dedent: 4
38
49
 
39
50
  .. NOTE::
40
- This truncator removes :py:class:`~pyrauli.PauliTerm` containing :math:`Y`. However, this is not an efficient.
51
+ This truncator removes :py:class:`~pyrauli.PauliTerm` containing :math:`Y`. However, this is not very efficient.
41
52
 
42
53
  Controlling when truncation is applied
43
54
  --------------------------------------
44
55
 
45
- **Solution:** Use a :py:class:`~pyrauli.SchedulingPolicy`.
56
+ Use a :py:class:`~pyrauli.SchedulingPolicy`.
46
57
 
47
58
  Scheduling Policies give you fine-grained control over when a :py:class:`~pyrauli.Truncator` is
48
59
  applied during the simulation. It can query a :py:class:`~pyrauli.SimulationState` object and other information to make a decision.
@@ -27,6 +27,9 @@ The :py:class:`~pyrauli.PBackend` class allows you to use ``pyrauli`` as a backe
27
27
  Qiskit ecosystem, enabling you to **transpile** and run circuits defined in Qiskit on the
28
28
  ``pyrauli`` simulator.
29
29
 
30
+ .. IMPORTANT::
31
+ If your Qiskit circuit uses unsupported gates, you need to transpile it, as you would for any other backend.
32
+
30
33
  Here is an example of transpilation to :py:class:`~pyrauli.PBackend`, relying on Qiskit ``PassManager``:
31
34
 
32
35
  .. literalinclude:: /../tests/test_backend.py
@@ -56,3 +59,13 @@ the hood.
56
59
  :start-after: # [estimator_complex]
57
60
  :end-before: # [estimator_complex]
58
61
  :dedent: 4
62
+
63
+ Qiskit and reverse qubit ordering
64
+ ---------------------------------
65
+
66
+ Qiskit uses reverse qubit ordering, while ``pyrauli`` use normal ordering. :py:class:`~pyrauli.from_qiskit` will not reverse the order of the qubit or Observable unless the `reverse=True` parameter is used.
67
+
68
+ However, when a qiskit observable is passed to the `.run` method, it will be reversed so that the output result match the output you would get on any other qiskit backend.
69
+
70
+ .. IMPORTANT::
71
+ You don't need to do anything different if using the qiskit compatible backend and qiskit observable. However, you may need to reverse the observable ordering when using :py:class:`~pyrauli.Circuit` and :py:class:`~pyrauli.from_qiskit` directly.
@@ -5,7 +5,7 @@ build-backend = "scikit_build_core.build"
5
5
 
6
6
  [project]
7
7
  name = "pyrauli"
8
- version = "0.2.1"
8
+ version = "0.3.0"
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"
@@ -29,6 +29,7 @@ using SchedulingPolicyPtr = std::shared_ptr<SchedulingPolicy>;
29
29
  using CoeffTruncatorPtr = std::shared_ptr<CoefficientTruncator<coeff_t>>;
30
30
  using WeightTruncatorPtr = std::shared_ptr<WeightTruncator>;
31
31
  using NeverTruncatorPtr = std::shared_ptr<NeverTruncator>;
32
+ using KeepNTruncatorPtr = std::shared_ptr<KeepNTruncator<coeff_t>>;
32
33
  using NeverPolicyPtr = std::shared_ptr<NeverPolicy>;
33
34
  using AlwaysBeforePolicyPtr = std::shared_ptr<AlwaysBeforeSplittingPolicy>;
34
35
  using AlwaysAfterPolicyPtr = std::shared_ptr<AlwaysAfterSplittingPolicy>;
@@ -211,6 +212,10 @@ PYBIND11_MODULE(_core, m) {
211
212
  py::class_<NeverTruncator, Truncator<coeff_t>, NeverTruncatorPtr>(m, "NeverTruncator",
212
213
  "A truncator that never removes any terms.")
213
214
  .def(py::init<>());
215
+ py::class_<KeepNTruncator<coeff_t>, Truncator<coeff_t>, KeepNTruncatorPtr>(
216
+ m, "KeepNTruncator",
217
+ "A truncator that removes least significant Pauli Terms, when their numbers is above a threshold.")
218
+ .def(py::init<std::size_t>());
214
219
  py::class_<LambdaTruncator, Truncator<coeff_t>, LambdaTruncatorPtr>(
215
220
  m, "LambdaTruncator", "A truncator that uses a Python function as a predicate.")
216
221
  .def(py::init<LambdaPredicate_t>());
@@ -7,7 +7,7 @@ from qiskit.circuit import QuantumCircuit
7
7
  from qiskit.quantum_info import SparsePauliOp
8
8
  import pyrauli
9
9
 
10
- def from_qiskit(qiskit_obj, noise_model: pyrauli.NoiseModel = None):
10
+ def from_qiskit(qiskit_obj, noise_model: pyrauli.NoiseModel = None, reverse=False):
11
11
  """
12
12
  Converts a Qiskit object to its pyrauli equivalent.
13
13
 
@@ -21,7 +21,7 @@ def from_qiskit(qiskit_obj, noise_model: pyrauli.NoiseModel = None):
21
21
  if isinstance(qiskit_obj, QuantumCircuit):
22
22
  return from_qiskit_qc(qiskit_obj, noise_model=noise_model)
23
23
  elif isinstance(qiskit_obj, SparsePauliOp):
24
- return from_qiskit_obs(qiskit_obj)
24
+ return from_qiskit_obs(qiskit_obj, reverse=reverse)
25
25
  else:
26
26
  raise ValueError(f"Can't convert object of type {type(qiskit_obj)} to pyrauli object.")
27
27
 
@@ -51,7 +51,7 @@ def from_qiskit_qc(qiskit_circuit: QuantumCircuit, noise_model: pyrauli.NoiseMod
51
51
 
52
52
  return pyrauli_circuit
53
53
 
54
- def from_qiskit_obs(qiskit_obs: SparsePauliOp) -> pyrauli.Observable:
54
+ def from_qiskit_obs(qiskit_obs: SparsePauliOp, reverse=False) -> pyrauli.Observable:
55
55
  """
56
56
  Translates a Qiskit SparsePauliOp to a pyrauli.Observable.
57
57
  """
@@ -59,6 +59,11 @@ def from_qiskit_obs(qiskit_obs: SparsePauliOp) -> pyrauli.Observable:
59
59
  for pauli_string, coeff in zip(qiskit_obs.paulis, qiskit_obs.coeffs):
60
60
  if coeff.imag != 0.:
61
61
  logging.warning("pyrauli observables don't support complex coefficients.")
62
- pt = pyrauli.PauliTerm(pauli_string.to_label(), float(coeff.real))
62
+
63
+ ps = pauli_string.to_label()
64
+ if reverse:
65
+ ps = ps[::-1]
66
+
67
+ pt = pyrauli.PauliTerm(ps, float(coeff.real))
63
68
  pts.append(pt)
64
69
  return pyrauli.Observable(pts)
@@ -122,7 +122,7 @@ class PyrauliEstimator(BaseEstimatorV2):
122
122
  """
123
123
  exp_values = []
124
124
  for obs in observables:
125
- pyrauli_obs = from_qiskit(obs)
125
+ pyrauli_obs = from_qiskit(obs, reverse=True)
126
126
  final_observable = pyrauli_circuit.run(pyrauli_obs)
127
127
  exp_values.append(final_observable.expectation_value())
128
128
  return exp_values
@@ -1,3 +1,4 @@
1
+ from dataclasses import replace
1
2
  import pytest
2
3
  import numpy as np
3
4
 
@@ -63,7 +64,7 @@ def test_estimator_multiple_observables_per_pub():
63
64
  qc.rz(np.pi/2, 0)
64
65
  qc.rz(np.pi/3, 1)
65
66
  qc.h([0, 1])
66
- observables = [SparsePauliOp("ZI"), SparsePauliOp("IZ")]
67
+ observables = [SparsePauliOp("IZ"), SparsePauliOp("ZI")]
67
68
 
68
69
  job = estimator.run([(qc, observables)])
69
70
  result = job.result()
@@ -82,7 +83,7 @@ def test_estimator_multiobs_multiparams():
82
83
  qc.rz(thetas[0], 0)
83
84
  qc.rz(thetas[1], 1)
84
85
  qc.h([0, 1])
85
- observables = [SparsePauliOp("ZI"), SparsePauliOp("IZ")]
86
+ observables = [SparsePauliOp("IZ"), SparsePauliOp("ZI")]
86
87
 
87
88
  job = estimator.run([(qc, observables, (np.pi/2, np.pi/3)), (qc, observables, (np.pi/3, np.pi/2))])
88
89
  result = job.result()
@@ -158,6 +159,31 @@ def test_random_circuits_match_aer_simulator():
158
159
 
159
160
  assert pyrauli_result == pytest.approx(qiskit_ev, abs=1e-6)
160
161
 
162
+ def test_random_circuits_random_obs_match_aer_simulator():
163
+ """Compares PyrauliEstimator against qiskit for many random circuits."""
164
+ pyrauli_estimator = PyrauliEstimator()
165
+ qiskit_estimator = StatevectorEstimator()
166
+
167
+ transpilation_backend = PBackend(num_qubits=4)
168
+ pm = generate_preset_pass_manager(backend=transpilation_backend, optimization_level=0)
169
+
170
+ np.random.seed(42) # for reproducibility
171
+ for i in range(8):
172
+ qc = random_circuit(num_qubits=4, depth=8, seed=i, max_operands=2)
173
+ obs = SparsePauliOp("".join(np.random.choice(["I", "X", "Y", "Z"], replace=True, size=4)))
174
+ transpiled_qc = pm.run(qc)
175
+
176
+ # Run on pyrauli
177
+ pyrauli_job = pyrauli_estimator.run([(transpiled_qc, obs)])
178
+ pyrauli_result = pyrauli_job.result()[0].data.evs[0]
179
+
180
+ # Run on Aer
181
+ qiskit_job = qiskit_estimator.run([(transpiled_qc, obs)])
182
+ qiskit_result = qiskit_job.result()
183
+ qiskit_ev = qiskit_result[0].data.evs #....
184
+
185
+ assert pyrauli_result == pytest.approx(qiskit_ev, abs=1e-6)
186
+
161
187
  @pytest.fixture
162
188
  def simple_pub():
163
189
  """Returns a simple PUB for running on the backend/estimator."""
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
File without changes
File without changes
File without changes