pyrauli 0.2.1__tar.gz → 0.3.1__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.
- {pyrauli-0.2.1 → pyrauli-0.3.1}/CMakeLists.txt +1 -1
- {pyrauli-0.2.1 → pyrauli-0.3.1}/PKG-INFO +1 -1
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/guides/how_to_circuit.rst +6 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/guides/how_to_complexity.rst +15 -4
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/guides/how_to_qiskit.rst +13 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/pyproject.toml +1 -1
- {pyrauli-0.2.1 → pyrauli-0.3.1}/src/pyrauli/__init__.py +2 -2
- {pyrauli-0.2.1 → pyrauli-0.3.1}/src/pyrauli/_core/bindings.cpp +5 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/src/pyrauli/converters.py +9 -4
- {pyrauli-0.2.1 → pyrauli-0.3.1}/src/pyrauli/estimator.py +1 -1
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/test_backend.py +28 -2
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/test_truncator.py +14 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/.clang-format +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/.github/workflows/benchmark.yml +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/.github/workflows/ci.yml +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/.github/workflows/doc.yml +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/.github/workflows/pypi.yml +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/.gitignore +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/LICENSE +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/README.md +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/benchmarks/test_benchmark_circuit.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/benchmarks/test_benchmark_observable.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/benchmarks/test_benchmark_qiskit.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/.gitignore +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/Makefile +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/_static/.gitkeep +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/_templates/.gitkeep +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/conf.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/explanation/theory.rst +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/guides/how_to_noise.rst +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/guides/how_to_observables.rst +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/index.rst +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/make.bat +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/reference/api.rst +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/requirements.txt +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/docs/tutorials/getting_started.rst +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/examples/qiskit_backend.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/src/pyrauli/backend.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/snippets/test_basic_circuit.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/snippets/test_observable_evolution.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/snippets/test_qiskit_backend_usage.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/snippets/test_readme.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/test_circuit.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/test_circuit_qiskit.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/test_noise_model.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/test_observable.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/test_observable_qiskit.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/test_pauli.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/test_policies.py +0 -0
- {pyrauli-0.2.1 → pyrauli-0.3.1}/tests/test_pyrauli.py +0 -0
@@ -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
|
-
|
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
|
-
|
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
|
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
|
-
|
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.
|
8
|
+
version = "0.3.1"
|
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"
|
@@ -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",
|
@@ -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
|
-
|
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("
|
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("
|
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."""
|
@@ -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
|
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
|
File without changes
|
File without changes
|
File without changes
|