From 810f6fc71a94893bd487ce5b526ba68f2b3940da Mon Sep 17 00:00:00 2001 From: Eugene Rublenko <16805621+stand-by@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:51:33 -0400 Subject: [PATCH 01/11] Arrange cpp stuff into cpp directory --- {examples => fast_pauli/cpp/examples}/01_pauli_op.cpp | 0 {examples => fast_pauli/cpp/examples}/02_pauli_op_multistate.cpp | 0 {examples => fast_pauli/cpp/examples}/03_summed_pauli_op.cpp | 0 {examples => fast_pauli/cpp/examples}/CMakeLists.txt | 0 {include => fast_pauli/cpp/include}/README.md | 0 {include => fast_pauli/cpp/include}/__factory.hpp | 0 {include => fast_pauli/cpp/include}/__pauli.hpp | 0 {include => fast_pauli/cpp/include}/__pauli_op.hpp | 0 {include => fast_pauli/cpp/include}/__pauli_string.hpp | 0 {include => fast_pauli/cpp/include}/__summed_pauli_op.hpp | 0 {include => fast_pauli/cpp/include}/fast_pauli.hpp | 0 {src => fast_pauli/cpp/src}/fast_pauli.cpp | 0 {tests => fast_pauli/cpp/tests}/CMakeLists.txt | 0 {tests => fast_pauli/cpp/tests}/test_factory.cpp | 0 {tests => fast_pauli/cpp/tests}/test_pauli.cpp | 0 {tests => fast_pauli/cpp/tests}/test_pauli_op.cpp | 0 {tests => fast_pauli/cpp/tests}/test_pauli_string.cpp | 0 {tests => fast_pauli/cpp/tests}/test_summed_pauli_op.cpp | 0 18 files changed, 0 insertions(+), 0 deletions(-) rename {examples => fast_pauli/cpp/examples}/01_pauli_op.cpp (100%) rename {examples => fast_pauli/cpp/examples}/02_pauli_op_multistate.cpp (100%) rename {examples => fast_pauli/cpp/examples}/03_summed_pauli_op.cpp (100%) rename {examples => fast_pauli/cpp/examples}/CMakeLists.txt (100%) rename {include => fast_pauli/cpp/include}/README.md (100%) rename {include => fast_pauli/cpp/include}/__factory.hpp (100%) rename {include => fast_pauli/cpp/include}/__pauli.hpp (100%) rename {include => fast_pauli/cpp/include}/__pauli_op.hpp (100%) rename {include => fast_pauli/cpp/include}/__pauli_string.hpp (100%) rename {include => fast_pauli/cpp/include}/__summed_pauli_op.hpp (100%) rename {include => fast_pauli/cpp/include}/fast_pauli.hpp (100%) rename {src => fast_pauli/cpp/src}/fast_pauli.cpp (100%) rename {tests => fast_pauli/cpp/tests}/CMakeLists.txt (100%) rename {tests => fast_pauli/cpp/tests}/test_factory.cpp (100%) rename {tests => fast_pauli/cpp/tests}/test_pauli.cpp (100%) rename {tests => fast_pauli/cpp/tests}/test_pauli_op.cpp (100%) rename {tests => fast_pauli/cpp/tests}/test_pauli_string.cpp (100%) rename {tests => fast_pauli/cpp/tests}/test_summed_pauli_op.cpp (100%) diff --git a/examples/01_pauli_op.cpp b/fast_pauli/cpp/examples/01_pauli_op.cpp similarity index 100% rename from examples/01_pauli_op.cpp rename to fast_pauli/cpp/examples/01_pauli_op.cpp diff --git a/examples/02_pauli_op_multistate.cpp b/fast_pauli/cpp/examples/02_pauli_op_multistate.cpp similarity index 100% rename from examples/02_pauli_op_multistate.cpp rename to fast_pauli/cpp/examples/02_pauli_op_multistate.cpp diff --git a/examples/03_summed_pauli_op.cpp b/fast_pauli/cpp/examples/03_summed_pauli_op.cpp similarity index 100% rename from examples/03_summed_pauli_op.cpp rename to fast_pauli/cpp/examples/03_summed_pauli_op.cpp diff --git a/examples/CMakeLists.txt b/fast_pauli/cpp/examples/CMakeLists.txt similarity index 100% rename from examples/CMakeLists.txt rename to fast_pauli/cpp/examples/CMakeLists.txt diff --git a/include/README.md b/fast_pauli/cpp/include/README.md similarity index 100% rename from include/README.md rename to fast_pauli/cpp/include/README.md diff --git a/include/__factory.hpp b/fast_pauli/cpp/include/__factory.hpp similarity index 100% rename from include/__factory.hpp rename to fast_pauli/cpp/include/__factory.hpp diff --git a/include/__pauli.hpp b/fast_pauli/cpp/include/__pauli.hpp similarity index 100% rename from include/__pauli.hpp rename to fast_pauli/cpp/include/__pauli.hpp diff --git a/include/__pauli_op.hpp b/fast_pauli/cpp/include/__pauli_op.hpp similarity index 100% rename from include/__pauli_op.hpp rename to fast_pauli/cpp/include/__pauli_op.hpp diff --git a/include/__pauli_string.hpp b/fast_pauli/cpp/include/__pauli_string.hpp similarity index 100% rename from include/__pauli_string.hpp rename to fast_pauli/cpp/include/__pauli_string.hpp diff --git a/include/__summed_pauli_op.hpp b/fast_pauli/cpp/include/__summed_pauli_op.hpp similarity index 100% rename from include/__summed_pauli_op.hpp rename to fast_pauli/cpp/include/__summed_pauli_op.hpp diff --git a/include/fast_pauli.hpp b/fast_pauli/cpp/include/fast_pauli.hpp similarity index 100% rename from include/fast_pauli.hpp rename to fast_pauli/cpp/include/fast_pauli.hpp diff --git a/src/fast_pauli.cpp b/fast_pauli/cpp/src/fast_pauli.cpp similarity index 100% rename from src/fast_pauli.cpp rename to fast_pauli/cpp/src/fast_pauli.cpp diff --git a/tests/CMakeLists.txt b/fast_pauli/cpp/tests/CMakeLists.txt similarity index 100% rename from tests/CMakeLists.txt rename to fast_pauli/cpp/tests/CMakeLists.txt diff --git a/tests/test_factory.cpp b/fast_pauli/cpp/tests/test_factory.cpp similarity index 100% rename from tests/test_factory.cpp rename to fast_pauli/cpp/tests/test_factory.cpp diff --git a/tests/test_pauli.cpp b/fast_pauli/cpp/tests/test_pauli.cpp similarity index 100% rename from tests/test_pauli.cpp rename to fast_pauli/cpp/tests/test_pauli.cpp diff --git a/tests/test_pauli_op.cpp b/fast_pauli/cpp/tests/test_pauli_op.cpp similarity index 100% rename from tests/test_pauli_op.cpp rename to fast_pauli/cpp/tests/test_pauli_op.cpp diff --git a/tests/test_pauli_string.cpp b/fast_pauli/cpp/tests/test_pauli_string.cpp similarity index 100% rename from tests/test_pauli_string.cpp rename to fast_pauli/cpp/tests/test_pauli_string.cpp diff --git a/tests/test_summed_pauli_op.cpp b/fast_pauli/cpp/tests/test_summed_pauli_op.cpp similarity index 100% rename from tests/test_summed_pauli_op.cpp rename to fast_pauli/cpp/tests/test_summed_pauli_op.cpp From 57ad7325d252fdddbe08fd7c30e58b002c090b25 Mon Sep 17 00:00:00 2001 From: Eugene Rublenko <16805621+stand-by@users.noreply.github.com> Date: Tue, 16 Jul 2024 18:22:30 -0400 Subject: [PATCH 02/11] Commit dunder files to enforce the structure --- benchmarks/__init__.py | 0 fast_pauli/py/pypauli/__init__.py | 0 fast_pauli/py/pypauli/operations.py | 0 fast_pauli/py/tests/__init__.py | 0 fast_pauli/py/tests/test_operations.py | 9 +++++++++ tests/__init__.py | 0 6 files changed, 9 insertions(+) create mode 100644 benchmarks/__init__.py create mode 100644 fast_pauli/py/pypauli/__init__.py create mode 100644 fast_pauli/py/pypauli/operations.py create mode 100644 fast_pauli/py/tests/__init__.py create mode 100644 fast_pauli/py/tests/test_operations.py create mode 100644 tests/__init__.py diff --git a/benchmarks/__init__.py b/benchmarks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fast_pauli/py/pypauli/__init__.py b/fast_pauli/py/pypauli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fast_pauli/py/pypauli/operations.py b/fast_pauli/py/pypauli/operations.py new file mode 100644 index 0000000..e69de29 diff --git a/fast_pauli/py/tests/__init__.py b/fast_pauli/py/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fast_pauli/py/tests/test_operations.py b/fast_pauli/py/tests/test_operations.py new file mode 100644 index 0000000..fd7b9c5 --- /dev/null +++ b/fast_pauli/py/tests/test_operations.py @@ -0,0 +1,9 @@ +import pytest + + +def test_dummy(): + assert True + + +if __name__ == "__main__": + pytest.main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 From 27d1168691885dc674c7870889938fe77b5dfb2a Mon Sep 17 00:00:00 2001 From: Eugene Rublenko <16805621+stand-by@users.noreply.github.com> Date: Tue, 16 Jul 2024 21:11:59 -0400 Subject: [PATCH 03/11] Port makefile and pyproject.toml from template repo and adjust it for our needs --- Makefile | 41 ++++++++++++++++++ pyproject.toml | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 Makefile create mode 100644 pyproject.toml diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9c355c6 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +.PHONY: build +build: + cmake -B build + cmake --build build --parallel + # TODO in general python build should internally trigger cmake, but for now + # let's keep cmake lines here as we don't have any python build process yet + python -m pip cache purge + python -m pip install --upgrade pip + python -m pip install ".[dev]" + python -m build . + +.PHONY: tests +tests: + ctest --test-dir build + python -m pytest fast_pauli/py/tests + python -m pytest tests + +.PHONY: clean +clean: + rm -rf build dist + +docs-build: + cd docs && \ + python -m sphinx -T -W --keep-going -b html -d _build/doctrees -D language=en . ./html + +livehtml: + sphinx-autobuild docs docs/_build/html + +.PHONY: clean-docs +clean-docs: + rm -rf docs/html docs/_build + +lint-check: + ruff check ./fast_pauli/py ./tests && \ + mypy ./fast_pauli/py ./tests + +lint-fix: + ruff check --fix ./fast_pauli/py ./tests + +lint-write: + ruff format ./fast_pauli/py ./tests \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a35c523 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,111 @@ +[build-system] +requires = [ + "setuptools>=64", + "build", + "setuptools_scm[toml]>=8", +] +build-backend = "setuptools.build_meta" + +[project] +name = "fast_pauli" +readme = "README.md" +description = "fast pauli" +dependencies = [ + "numpy", + "scipy", +] +dynamic = ["version"] + +[project.optional-dependencies] +dev = [ + "clang-format", + "cmake-format", + "pre-commit", + "build", + "mypy", + "ruff", + "pytest", + "setuptools_scm[toml]>=8", + "sphinx", + "sphinx_rtd_theme", + "sphinx-autobuild", +] +examples = [ +] + +[tool.setuptools] +py-modules = ["fast_pauli"] + +[tool.setuptools_scm] +version_file = "fast_pauli/__version__.py" + +[tool.mypy] +warn_redundant_casts = true +warn_unused_ignores = true + +# Needed because of bug in MyPy +disallow_subclassing_any = false + +mypy_path = "stubs" + +disallow_untyped_calls = true +disallow_untyped_defs = true +check_untyped_defs = true +warn_return_any = true +no_implicit_optional = true +strict_optional = true +ignore_missing_imports = true + +exclude = [] + +[tool.ruff] +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + ".venv", + "fast_pauli/__version__.py", +] + +line-length = 88 +indent-width = 4 + +target-version="py38" + +[tool.ruff.lint] + +# Ruff Rules https://docs.astral.sh/ruff/rules/ +# F - PyFlakes (https://docs.astral.sh/ruff/rules/#pyflakes-f) +# E, W - pycodestyle (https://docs.astral.sh/ruff/rules/#pycodestyle-e-w) +# I - Isort (https://docs.astral.sh/ruff/rules/#isort-i) +# N - PEP-8 Naming (https://docs.astral.sh/ruff/rules/#pep8-naming-n) +# D - pydocstyle (https://docs.astral.sh/ruff/rules/#pydocstyle-d) +# YTT - flake8-2020 (https://docs.astral.sh/ruff/rules/#flake8-2020-ytt) +# ASYNC flake8-async (https://docs.astral.sh/ruff/rules/#flake8-async-async) + +select = ["F", "E", "W", "I", "N", "D", "YTT", "ASYNC"] + +ignore = [] From 5b0188a301e4bcb2998750eb7808e87e98e0a4b7 Mon Sep 17 00:00:00 2001 From: Eugene Rublenko <16805621+stand-by@users.noreply.github.com> Date: Tue, 16 Jul 2024 21:15:01 -0400 Subject: [PATCH 04/11] Point cmake to new directory with cpp files --- CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a5dd3f5..94d6e52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,8 +48,8 @@ find_package(OpenMP REQUIRED) # Our primary target add_library(fast_pauli INTERFACE) -target_include_directories(fast_pauli - INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include/) +target_include_directories( + fast_pauli INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/fast_pauli/cpp/include/) target_link_libraries(fast_pauli INTERFACE fmt::fmt mdspan OpenMP::OpenMP_CXX) target_compile_options( fast_pauli @@ -66,7 +66,8 @@ target_compile_options( # Testing include(CTest) enable_testing() -add_subdirectory(tests) +# TODO use proper variable for project root +add_subdirectory(fast_pauli/cpp/tests) # Examples -add_subdirectory(examples) +add_subdirectory(fast_pauli/cpp/examples) From 82e4007a46a48628003305e4daf6b1359800b2af Mon Sep 17 00:00:00 2001 From: Eugene Rublenko <16805621+stand-by@users.noreply.github.com> Date: Thu, 18 Jul 2024 11:34:45 -0400 Subject: [PATCH 05/11] Attemp to fix CI/CD --- .github/workflows/all_push.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/all_push.yml b/.github/workflows/all_push.yml index 2d30e74..a1ec458 100644 --- a/.github/workflows/all_push.yml +++ b/.github/workflows/all_push.yml @@ -29,17 +29,18 @@ jobs: # Build your program with the given configuration run: | cmake --build ${{github.workspace}}/build --verbose --parallel - du build/tests/test_pauli_op - name: Test C++ env: OMP_NUM_THREADS: 2 + CPP_TEST_DIR: build/fast_pauli/cpp/tests run: | + du ${CPP_TEST_DIR}/test_pauli_op # ctest --test-dir build --verbose # TODO not using bc of PauliOp problems on CI - ./build/tests/test_factory - ./build/tests/test_pauli - ./build/tests/test_pauli_op --test-case-exclude="*multistring*" - ./build/tests/test_pauli_string - ./build/tests/test_summed_pauli_op + ./${CPP_TEST_DIR}/test_factory + ./${CPP_TEST_DIR}/test_pauli + ./${CPP_TEST_DIR}/test_pauli_op --test-case-exclude="*multistring*" + ./${CPP_TEST_DIR}/test_pauli_string + ./${CPP_TEST_DIR}/test_summed_pauli_op # - name: Test Python # run: PYTHONPATH=build:$PYTHONPATH pytest -v test From 1ad8db7b2d38e07950583d5ed4aa94e9d0d7eb72 Mon Sep 17 00:00:00 2001 From: Eugene Rublenko <16805621+stand-by@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:17:58 -0400 Subject: [PATCH 06/11] Move python files to pypauli module --- benchmarks/pauli_operations.py | 103 ------------ benchmarks/test_pauli_operations.py | 159 ------------------ .../py/pypauli/helpers.py | 0 fast_pauli/py/pypauli/operations.py | 103 ++++++++++++ fast_pauli/py/tests/test_operations.py | 156 ++++++++++++++++- 5 files changed, 256 insertions(+), 265 deletions(-) delete mode 100644 benchmarks/pauli_operations.py delete mode 100644 benchmarks/test_pauli_operations.py rename benchmarks/pauli_helpers.py => fast_pauli/py/pypauli/helpers.py (100%) diff --git a/benchmarks/pauli_operations.py b/benchmarks/pauli_operations.py deleted file mode 100644 index c9db78f..0000000 --- a/benchmarks/pauli_operations.py +++ /dev/null @@ -1,103 +0,0 @@ -import numpy as np - - -class PauliString: - def __init__(self, string: str) -> None: - if not all([c in "IXYZ" for c in string]): - raise ValueError(f"Invalid pauli string {string}") - - self.string = string - self.dim = 1 << len(string) - self.weight = len(string) - string.count("I") - - def dense(self) -> np.ndarray: - columns, values = compose_sparse_pauli(self.string) - - matrix = np.zeros((columns.size, values.size), dtype=np.complex128) - matrix[np.arange(columns.size), columns] = values - return matrix - - def multiply(self, state: np.ndarray) -> np.ndarray: - if state.shape[0] != self.dim or state.ndim > 2: - raise ValueError(f"Provided state has inconsistent shape {state.shape}") - - columns, values = compose_sparse_pauli(self.string) - - if state.ndim == 2: - return values[:, np.newaxis] * state[columns] - else: - return values * state[columns] - - -def compose_sparse_pauli(string: str) -> tuple[np.ndarray, np.ndarray]: - n_qubits = len(string) - n_vals = 1 << n_qubits - n_ys = string.count("Y") - - # initialize cols array with zeros as we need first element to be 0 - cols = np.zeros(n_vals, dtype=np.int32) - vals = np.empty(n_vals, dtype=np.complex128) - - for p in string: - cols[0] <<= 1 - if p == "X" or p == "Y": - cols[0] += 1 - - match n_ys % 4: - case 0: - vals[0] = 1.0 - case 1: - vals[0] = -1.0j - case 2: - vals[0] = -1.0 - case 3: - vals[0] = 1.0j - - for q in range(n_qubits): - p = string[n_qubits - q - 1] - pow_of_two = 1 << q - - new_slice = slice(pow_of_two, 2 * pow_of_two) - old_slice = slice(0, pow_of_two) - - match p: - case "I": - cols[new_slice] = cols[old_slice] + pow_of_two - vals[new_slice] = vals[old_slice] - case "X": - cols[new_slice] = cols[old_slice] - pow_of_two - vals[new_slice] = vals[old_slice] - case "Y": - cols[new_slice] = cols[old_slice] - pow_of_two - vals[new_slice] = -vals[old_slice] - case "Z": - cols[new_slice] = cols[old_slice] + pow_of_two - vals[new_slice] = -vals[old_slice] - - return cols, vals - - -def compose_sparse_diag_pauli(string) -> np.ndarray: - if "X" in string or "Y" in string: - raise ValueError("Pauli string must contain only I and Z characters") - - n_qubits = len(string) - n_vals = 1 << n_qubits - - # initialize vals array with ones as we need first element to be 1 - vals = np.ones(n_vals, dtype=np.complex128) - - for q in range(n_qubits): - p = string[n_qubits - q - 1] - pow_of_two = 1 << q - - new_slice = slice(pow_of_two, 2 * pow_of_two) - old_slice = slice(0, pow_of_two) - - match p: - case "I": - vals[new_slice] = vals[old_slice] - case "Z": - vals[new_slice] = -vals[old_slice] - - return vals diff --git a/benchmarks/test_pauli_operations.py b/benchmarks/test_pauli_operations.py deleted file mode 100644 index df7f300..0000000 --- a/benchmarks/test_pauli_operations.py +++ /dev/null @@ -1,159 +0,0 @@ -import pytest -import numpy as np -from itertools import permutations, chain - -from pauli_operations import ( - PauliString, - compose_sparse_pauli, - compose_sparse_diag_pauli, -) -from pauli_helpers import pauli_matrices, naive_pauli_converter - - -@pytest.fixture -def paulis(): - return pauli_matrices() - - -def test_pauli_string(paulis): - for p in ["I", "X", "Y", "Z"]: - ps = PauliString(p) - assert ps.dim == 2 - assert ps.weight == 1 or p == "I" - np.testing.assert_array_equal(ps.dense(), paulis[p]) - np.testing.assert_array_equal(naive_pauli_converter(p), paulis[p]) - - ps = PauliString("III") - assert ps.dim == 8 - assert ps.weight == 0 - np.testing.assert_array_equal(ps.dense(), np.eye(8)) - np.testing.assert_array_equal(naive_pauli_converter(ps.string), np.eye(8)) - - ps = PauliString(string="IZ") - assert ps.dim == 4 - assert ps.weight == 1 - np.testing.assert_array_equal(ps.dense(), np.kron(paulis["I"], paulis["Z"])) - np.testing.assert_array_equal( - naive_pauli_converter(ps.string), np.kron(paulis["I"], paulis["Z"]) - ) - - ps = PauliString(string="XYZ") - assert ps.dim == 8 - assert ps.weight == 3 - np.testing.assert_array_equal( - ps.dense(), np.kron(paulis["X"], np.kron(paulis["Y"], paulis["Z"])) - ) - np.testing.assert_array_equal( - naive_pauli_converter(ps.string), - np.kron(paulis["X"], np.kron(paulis["Y"], paulis["Z"])), - ) - - assert PauliString("XYIZXYZ").dim == 2**7 - assert PauliString("XYIZXYZ").weight == 6 - assert PauliString("XXIYYIZZ").dim == 2**8 - assert PauliString("XXIYYIZZ").weight == 6 - assert PauliString("ZIXIZYXXY").dim == 2**9 - assert PauliString("ZIXIZYXXY").weight == 7 - - -def test_sparse_pauli_composer(paulis): - ps = PauliString("II") - assert ps.dim == 4 - np.testing.assert_array_equal(ps.dense(), np.eye(4)) - np.testing.assert_array_equal(compose_sparse_diag_pauli(ps.string), np.ones(4)) - - ps = PauliString("IIII") - assert ps.dim == 16 - np.testing.assert_array_equal(ps.dense(), np.eye(16)) - np.testing.assert_array_equal(compose_sparse_diag_pauli(ps.string), np.ones(16)) - - ps = PauliString("XXX") - assert ps.dim == 8 - np.testing.assert_array_equal(ps.dense(), np.fliplr(np.eye(8))) - - ps = PauliString("IY") - np.testing.assert_array_equal( - ps.dense(), - np.block([[paulis["Y"], np.zeros((2, 2))], [np.zeros((2, 2)), paulis["Y"]]]), - ) - - ps = PauliString("IZ") - np.testing.assert_array_equal( - ps.dense(), - np.block([[paulis["Z"], np.zeros((2, 2))], [np.zeros((2, 2)), paulis["Z"]]]), - ) - - np.testing.assert_array_equal( - compose_sparse_diag_pauli("IZI"), - compose_sparse_pauli("IZI")[1], - ) - - np.testing.assert_array_equal( - compose_sparse_diag_pauli("ZIZI"), - compose_sparse_pauli("ZIZI")[1], - ) - - np.testing.assert_array_equal( - compose_sparse_diag_pauli("ZZZIII"), - compose_sparse_pauli("ZZZIII")[1], - ) - - -def test_sparse_pauli_composer_equivalence(): - for c in ["I", "X", "Y", "Z"]: - np.testing.assert_array_equal( - PauliString(c).dense(), - naive_pauli_converter(c), - ) - - for s in permutations("XYZ", 2): - s = "".join(s) - np.testing.assert_array_equal( - PauliString(s).dense(), - naive_pauli_converter(s), - ) - - for s in permutations("IXYZ", 3): - s = "".join(s) - np.testing.assert_array_equal( - PauliString(s).dense(), - naive_pauli_converter(s), - ) - - ixyz = PauliString("IXYZ").dense() - np.testing.assert_array_equal(ixyz, naive_pauli_converter("IXYZ")) - - zyxi = PauliString("ZYXI").dense() - np.testing.assert_array_equal(zyxi, naive_pauli_converter("ZYXI")) - - assert np.abs(ixyz - zyxi).sum().sum() > 1e-10 - - for s in ["XYIZXYZ", "XXIYYIZZ", "ZIXIZYXX"]: - np.testing.assert_array_equal(PauliString(s).dense(), naive_pauli_converter(s)) - - -def test_sparse_pauli_multiply(): - rng = np.random.default_rng(321) - - for s in chain( - list("IXYZ"), list(permutations("IXYZ", 3)), ["XYIZXYZ", "XXIYYIZZ", "ZIXIZYXX"] - ): - s = "".join(s) - n = 2 ** len(s) - psi = rng.random(n) - psi_batch = rng.random((n, 21)) - - np.testing.assert_allclose( - PauliString(s).multiply(psi), - naive_pauli_converter(s).dot(psi), - atol=1e-15, - ) - np.testing.assert_allclose( - PauliString(s).multiply(psi_batch), - naive_pauli_converter(s) @ psi_batch, - atol=1e-15, - ) - - -if __name__ == "__main__": - pytest.main([__file__]) diff --git a/benchmarks/pauli_helpers.py b/fast_pauli/py/pypauli/helpers.py similarity index 100% rename from benchmarks/pauli_helpers.py rename to fast_pauli/py/pypauli/helpers.py diff --git a/fast_pauli/py/pypauli/operations.py b/fast_pauli/py/pypauli/operations.py index e69de29..c9db78f 100644 --- a/fast_pauli/py/pypauli/operations.py +++ b/fast_pauli/py/pypauli/operations.py @@ -0,0 +1,103 @@ +import numpy as np + + +class PauliString: + def __init__(self, string: str) -> None: + if not all([c in "IXYZ" for c in string]): + raise ValueError(f"Invalid pauli string {string}") + + self.string = string + self.dim = 1 << len(string) + self.weight = len(string) - string.count("I") + + def dense(self) -> np.ndarray: + columns, values = compose_sparse_pauli(self.string) + + matrix = np.zeros((columns.size, values.size), dtype=np.complex128) + matrix[np.arange(columns.size), columns] = values + return matrix + + def multiply(self, state: np.ndarray) -> np.ndarray: + if state.shape[0] != self.dim or state.ndim > 2: + raise ValueError(f"Provided state has inconsistent shape {state.shape}") + + columns, values = compose_sparse_pauli(self.string) + + if state.ndim == 2: + return values[:, np.newaxis] * state[columns] + else: + return values * state[columns] + + +def compose_sparse_pauli(string: str) -> tuple[np.ndarray, np.ndarray]: + n_qubits = len(string) + n_vals = 1 << n_qubits + n_ys = string.count("Y") + + # initialize cols array with zeros as we need first element to be 0 + cols = np.zeros(n_vals, dtype=np.int32) + vals = np.empty(n_vals, dtype=np.complex128) + + for p in string: + cols[0] <<= 1 + if p == "X" or p == "Y": + cols[0] += 1 + + match n_ys % 4: + case 0: + vals[0] = 1.0 + case 1: + vals[0] = -1.0j + case 2: + vals[0] = -1.0 + case 3: + vals[0] = 1.0j + + for q in range(n_qubits): + p = string[n_qubits - q - 1] + pow_of_two = 1 << q + + new_slice = slice(pow_of_two, 2 * pow_of_two) + old_slice = slice(0, pow_of_two) + + match p: + case "I": + cols[new_slice] = cols[old_slice] + pow_of_two + vals[new_slice] = vals[old_slice] + case "X": + cols[new_slice] = cols[old_slice] - pow_of_two + vals[new_slice] = vals[old_slice] + case "Y": + cols[new_slice] = cols[old_slice] - pow_of_two + vals[new_slice] = -vals[old_slice] + case "Z": + cols[new_slice] = cols[old_slice] + pow_of_two + vals[new_slice] = -vals[old_slice] + + return cols, vals + + +def compose_sparse_diag_pauli(string) -> np.ndarray: + if "X" in string or "Y" in string: + raise ValueError("Pauli string must contain only I and Z characters") + + n_qubits = len(string) + n_vals = 1 << n_qubits + + # initialize vals array with ones as we need first element to be 1 + vals = np.ones(n_vals, dtype=np.complex128) + + for q in range(n_qubits): + p = string[n_qubits - q - 1] + pow_of_two = 1 << q + + new_slice = slice(pow_of_two, 2 * pow_of_two) + old_slice = slice(0, pow_of_two) + + match p: + case "I": + vals[new_slice] = vals[old_slice] + case "Z": + vals[new_slice] = -vals[old_slice] + + return vals diff --git a/fast_pauli/py/tests/test_operations.py b/fast_pauli/py/tests/test_operations.py index fd7b9c5..479befd 100644 --- a/fast_pauli/py/tests/test_operations.py +++ b/fast_pauli/py/tests/test_operations.py @@ -1,9 +1,159 @@ import pytest +import numpy as np +from itertools import permutations, chain +from pypauli.operations import ( + PauliString, + compose_sparse_pauli, + compose_sparse_diag_pauli, +) +from pypauli.helpers import pauli_matrices, naive_pauli_converter -def test_dummy(): - assert True + +@pytest.fixture +def paulis(): + return pauli_matrices() + + +def test_pauli_string(paulis): + for p in ["I", "X", "Y", "Z"]: + ps = PauliString(p) + assert ps.dim == 2 + assert ps.weight == 1 or p == "I" + np.testing.assert_array_equal(ps.dense(), paulis[p]) + np.testing.assert_array_equal(naive_pauli_converter(p), paulis[p]) + + ps = PauliString("III") + assert ps.dim == 8 + assert ps.weight == 0 + np.testing.assert_array_equal(ps.dense(), np.eye(8)) + np.testing.assert_array_equal(naive_pauli_converter(ps.string), np.eye(8)) + + ps = PauliString(string="IZ") + assert ps.dim == 4 + assert ps.weight == 1 + np.testing.assert_array_equal(ps.dense(), np.kron(paulis["I"], paulis["Z"])) + np.testing.assert_array_equal( + naive_pauli_converter(ps.string), np.kron(paulis["I"], paulis["Z"]) + ) + + ps = PauliString(string="XYZ") + assert ps.dim == 8 + assert ps.weight == 3 + np.testing.assert_array_equal( + ps.dense(), np.kron(paulis["X"], np.kron(paulis["Y"], paulis["Z"])) + ) + np.testing.assert_array_equal( + naive_pauli_converter(ps.string), + np.kron(paulis["X"], np.kron(paulis["Y"], paulis["Z"])), + ) + + assert PauliString("XYIZXYZ").dim == 2**7 + assert PauliString("XYIZXYZ").weight == 6 + assert PauliString("XXIYYIZZ").dim == 2**8 + assert PauliString("XXIYYIZZ").weight == 6 + assert PauliString("ZIXIZYXXY").dim == 2**9 + assert PauliString("ZIXIZYXXY").weight == 7 + + +def test_sparse_pauli_composer(paulis): + ps = PauliString("II") + assert ps.dim == 4 + np.testing.assert_array_equal(ps.dense(), np.eye(4)) + np.testing.assert_array_equal(compose_sparse_diag_pauli(ps.string), np.ones(4)) + + ps = PauliString("IIII") + assert ps.dim == 16 + np.testing.assert_array_equal(ps.dense(), np.eye(16)) + np.testing.assert_array_equal(compose_sparse_diag_pauli(ps.string), np.ones(16)) + + ps = PauliString("XXX") + assert ps.dim == 8 + np.testing.assert_array_equal(ps.dense(), np.fliplr(np.eye(8))) + + ps = PauliString("IY") + np.testing.assert_array_equal( + ps.dense(), + np.block([[paulis["Y"], np.zeros((2, 2))], [np.zeros((2, 2)), paulis["Y"]]]), + ) + + ps = PauliString("IZ") + np.testing.assert_array_equal( + ps.dense(), + np.block([[paulis["Z"], np.zeros((2, 2))], [np.zeros((2, 2)), paulis["Z"]]]), + ) + + np.testing.assert_array_equal( + compose_sparse_diag_pauli("IZI"), + compose_sparse_pauli("IZI")[1], + ) + + np.testing.assert_array_equal( + compose_sparse_diag_pauli("ZIZI"), + compose_sparse_pauli("ZIZI")[1], + ) + + np.testing.assert_array_equal( + compose_sparse_diag_pauli("ZZZIII"), + compose_sparse_pauli("ZZZIII")[1], + ) + + +def test_sparse_pauli_composer_equivalence(): + for c in ["I", "X", "Y", "Z"]: + np.testing.assert_array_equal( + PauliString(c).dense(), + naive_pauli_converter(c), + ) + + for s in permutations("XYZ", 2): + s = "".join(s) + np.testing.assert_array_equal( + PauliString(s).dense(), + naive_pauli_converter(s), + ) + + for s in permutations("IXYZ", 3): + s = "".join(s) + np.testing.assert_array_equal( + PauliString(s).dense(), + naive_pauli_converter(s), + ) + + ixyz = PauliString("IXYZ").dense() + np.testing.assert_array_equal(ixyz, naive_pauli_converter("IXYZ")) + + zyxi = PauliString("ZYXI").dense() + np.testing.assert_array_equal(zyxi, naive_pauli_converter("ZYXI")) + + assert np.abs(ixyz - zyxi).sum().sum() > 1e-10 + + for s in ["XYIZXYZ", "XXIYYIZZ", "ZIXIZYXX"]: + np.testing.assert_array_equal(PauliString(s).dense(), naive_pauli_converter(s)) + + +def test_sparse_pauli_multiply(): + rng = np.random.default_rng(321) + + for s in chain( + list("IXYZ"), list(permutations("IXYZ", 3)), ["XYIZXYZ", "XXIYYIZZ", "ZIXIZYXX"] + ): + s = "".join(s) + n = 2 ** len(s) + psi = rng.random(n) + psi_batch = rng.random((n, 21)) + + np.testing.assert_allclose( + PauliString(s).multiply(psi), + naive_pauli_converter(s).dot(psi), + atol=1e-15, + ) + np.testing.assert_allclose( + PauliString(s).multiply(psi_batch), + naive_pauli_converter(s) @ psi_batch, + atol=1e-15, + ) if __name__ == "__main__": - pytest.main() + pytest.main([__file__]) From 65c328b10eda24a6ed9251b21b7820622f849577 Mon Sep 17 00:00:00 2001 From: Eugene Rublenko <16805621+stand-by@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:19:58 -0400 Subject: [PATCH 07/11] Minor changes in pyproject and gitignore --- .gitignore | 2 ++ pyproject.toml | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index aaf460a..866cb79 100644 --- a/.gitignore +++ b/.gitignore @@ -72,10 +72,12 @@ __pycache__ *.xwm # Misc +.venv .vscode .coverage *.zip *_package +venv logs scratch notes diff --git a/pyproject.toml b/pyproject.toml index a35c523..b02dbca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,11 @@ examples = [ ] [tool.setuptools] -py-modules = ["fast_pauli"] +packages = ["fast_pauli", "fast_pauli.pypauli"] + +[tool.setuptools.package-dir] +fast_pauli = "fast_pauli" +"fast_pauli.pypauli" = "fast_pauli/py/pypauli" [tool.setuptools_scm] version_file = "fast_pauli/__version__.py" From f2ed6c28e55507ebb7323dceb18a8ba35b61993b Mon Sep 17 00:00:00 2001 From: Eugene Rublenko <16805621+stand-by@users.noreply.github.com> Date: Thu, 18 Jul 2024 19:12:25 -0400 Subject: [PATCH 08/11] Train copilot to make ruff satisfied --- .github/compiler_setup.py | 1 + benchmarks/__init__.py | 1 + fast_pauli/py/pypauli/__init__.py | 1 + fast_pauli/py/pypauli/helpers.py | 26 ++++++++++++++ fast_pauli/py/pypauli/operations.py | 47 ++++++++++++++++++++++++++ fast_pauli/py/tests/__init__.py | 1 + fast_pauli/py/tests/test_operations.py | 17 +++++++--- tests/__init__.py | 1 + 8 files changed, 90 insertions(+), 5 deletions(-) diff --git a/.github/compiler_setup.py b/.github/compiler_setup.py index 7bd7d3d..8f0b80f 100644 --- a/.github/compiler_setup.py +++ b/.github/compiler_setup.py @@ -1,3 +1,4 @@ +# noqa: D100 import os import sys diff --git a/benchmarks/__init__.py b/benchmarks/__init__.py index e69de29..e9bff40 100644 --- a/benchmarks/__init__.py +++ b/benchmarks/__init__.py @@ -0,0 +1 @@ +"""Benchmarking module to compare C++ implementation against numpy.""" diff --git a/fast_pauli/py/pypauli/__init__.py b/fast_pauli/py/pypauli/__init__.py index e69de29..7854146 100644 --- a/fast_pauli/py/pypauli/__init__.py +++ b/fast_pauli/py/pypauli/__init__.py @@ -0,0 +1 @@ +"""pypauli module provides implementations to benchmark and test c++ library.""" diff --git a/fast_pauli/py/pypauli/helpers.py b/fast_pauli/py/pypauli/helpers.py index 27cf0e4..f7dbf1a 100644 --- a/fast_pauli/py/pypauli/helpers.py +++ b/fast_pauli/py/pypauli/helpers.py @@ -1,7 +1,20 @@ +"""Separate file with helper functions for Pauli matrices.""" + import numpy as np def pauli_matrices() -> dict: + """Provide a dictionary containing the Pauli matrices. + + The Pauli matrices are a set of 2x2 matrices commonly used in quantum mechanics. + This function returns a dictionary with keys representing the matrix names + ("I", "X", "Y", "Z", 0, 1, 2, 3) and values representing the corresponding matrix. + + Returns + ------- + dict: A dictionary containing the Pauli matrices. + + """ s0 = np.array([[1, 0], [0, 1]], dtype=np.complex128) s1 = np.array([[0, 1], [1, 0]], dtype=np.complex128) s2 = np.array([[0, -1j], [1j, 0]], dtype=np.complex128) @@ -10,6 +23,19 @@ def pauli_matrices() -> dict: def naive_pauli_converter(string: str) -> np.ndarray: + """Convert Pauli string representation into corresponding dense matrix. + + Parameters + ---------- + string : str + The string representation of Pauli string. + + Returns + ------- + np.ndarray + The matrix representation of the Pauli string. + + """ paulis = pauli_matrices() matrix = paulis[string[-1]] for p in reversed(string[:-1]): diff --git a/fast_pauli/py/pypauli/operations.py b/fast_pauli/py/pypauli/operations.py index c9db78f..bec1c44 100644 --- a/fast_pauli/py/pypauli/operations.py +++ b/fast_pauli/py/pypauli/operations.py @@ -1,8 +1,20 @@ +"""Efficient operations on Pauli string using numpy.""" + import numpy as np class PauliString: + """Class representing a Pauli string with efficient operations.""" + def __init__(self, string: str) -> None: + """Initialize the PauliString object. + + Args: + ---- + string: The input string representing the Pauli string. + Must contain only I, X, Y, Z characters. + + """ if not all([c in "IXYZ" for c in string]): raise ValueError(f"Invalid pauli string {string}") @@ -11,6 +23,7 @@ def __init__(self, string: str) -> None: self.weight = len(string) - string.count("I") def dense(self) -> np.ndarray: + """Return the dense matrix representation of the Pauli string.""" columns, values = compose_sparse_pauli(self.string) matrix = np.zeros((columns.size, values.size), dtype=np.complex128) @@ -18,6 +31,17 @@ def dense(self) -> np.ndarray: return matrix def multiply(self, state: np.ndarray) -> np.ndarray: + """Efficient multiplication of Pauli string with a given state. + + Args: + ---- + state: The input state as a numpy array. + + Returns: + ------- + The result of multiplying the Pauli string with the state. + + """ if state.shape[0] != self.dim or state.ndim > 2: raise ValueError(f"Provided state has inconsistent shape {state.shape}") @@ -30,6 +54,18 @@ def multiply(self, state: np.ndarray) -> np.ndarray: def compose_sparse_pauli(string: str) -> tuple[np.ndarray, np.ndarray]: + """Produce sparse representation of the pauli string. + + Args: + ---- + string: The input string representing the Pauli string. + Must contain only I, X, Y, Z characters. + + Returns: + ------- + A tuple containing the column numbers and values of the sparse matrix. + + """ n_qubits = len(string) n_vals = 1 << n_qubits n_ys = string.count("Y") @@ -78,6 +114,17 @@ def compose_sparse_pauli(string: str) -> tuple[np.ndarray, np.ndarray]: def compose_sparse_diag_pauli(string) -> np.ndarray: + """Produce sparse representation of diagonal pauli string. + + Args: + ---- + string: A Pauli string containing only 'I' and 'Z' characters. + + Returns: + ------- + np.ndarray: diagonal values from resulting sparse matrix. + + """ if "X" in string or "Y" in string: raise ValueError("Pauli string must contain only I and Z characters") diff --git a/fast_pauli/py/tests/__init__.py b/fast_pauli/py/tests/__init__.py index e69de29..699302a 100644 --- a/fast_pauli/py/tests/__init__.py +++ b/fast_pauli/py/tests/__init__.py @@ -0,0 +1 @@ +"""Unit tests for pypauli module.""" diff --git a/fast_pauli/py/tests/test_operations.py b/fast_pauli/py/tests/test_operations.py index 479befd..07e5343 100644 --- a/fast_pauli/py/tests/test_operations.py +++ b/fast_pauli/py/tests/test_operations.py @@ -1,21 +1,25 @@ -import pytest -import numpy as np -from itertools import permutations, chain +"""Tests for pauli string and related operations.""" +from itertools import chain, permutations + +import numpy as np +import pytest +from pypauli.helpers import naive_pauli_converter, pauli_matrices from pypauli.operations import ( PauliString, - compose_sparse_pauli, compose_sparse_diag_pauli, + compose_sparse_pauli, ) -from pypauli.helpers import pauli_matrices, naive_pauli_converter @pytest.fixture def paulis(): + """Fixture to provide dict with Pauli matrices.""" return pauli_matrices() def test_pauli_string(paulis): + """Test the PauliString class.""" for p in ["I", "X", "Y", "Z"]: ps = PauliString(p) assert ps.dim == 2 @@ -57,6 +61,7 @@ def test_pauli_string(paulis): def test_sparse_pauli_composer(paulis): + """Test sparsepauli composer functions.""" ps = PauliString("II") assert ps.dim == 4 np.testing.assert_array_equal(ps.dense(), np.eye(4)) @@ -100,6 +105,7 @@ def test_sparse_pauli_composer(paulis): def test_sparse_pauli_composer_equivalence(): + """Test the equivalence of sparse Pauli composer with naive method.""" for c in ["I", "X", "Y", "Z"]: np.testing.assert_array_equal( PauliString(c).dense(), @@ -133,6 +139,7 @@ def test_sparse_pauli_composer_equivalence(): def test_sparse_pauli_multiply(): + """Test the equivalence of multiply method that relies on sparse Pauli composer.""" rng = np.random.default_rng(321) for s in chain( diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..007b624 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1 @@ +"""Test module for testing C++ library against python implementation.""" From c4ff2bbb63863e06fdf3b9620bb8c9234b420d3f Mon Sep 17 00:00:00 2001 From: Eugene Rublenko <16805621+stand-by@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:23:47 -0400 Subject: [PATCH 09/11] Address comments --- .github/workflows/all_push.yml | 1 - Makefile | 13 +------------ fast_pauli/py/pypauli/operations.py | 6 +++--- pyproject.toml | 19 +++++++++++++++++-- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/.github/workflows/all_push.yml b/.github/workflows/all_push.yml index a1ec458..e37c887 100644 --- a/.github/workflows/all_push.yml +++ b/.github/workflows/all_push.yml @@ -34,7 +34,6 @@ jobs: OMP_NUM_THREADS: 2 CPP_TEST_DIR: build/fast_pauli/cpp/tests run: | - du ${CPP_TEST_DIR}/test_pauli_op # ctest --test-dir build --verbose # TODO not using bc of PauliOp problems on CI ./${CPP_TEST_DIR}/test_factory ./${CPP_TEST_DIR}/test_pauli diff --git a/Makefile b/Makefile index 9c355c6..8bc3d04 100644 --- a/Makefile +++ b/Makefile @@ -19,17 +19,6 @@ tests: clean: rm -rf build dist -docs-build: - cd docs && \ - python -m sphinx -T -W --keep-going -b html -d _build/doctrees -D language=en . ./html - -livehtml: - sphinx-autobuild docs docs/_build/html - -.PHONY: clean-docs -clean-docs: - rm -rf docs/html docs/_build - lint-check: ruff check ./fast_pauli/py ./tests && \ mypy ./fast_pauli/py ./tests @@ -38,4 +27,4 @@ lint-fix: ruff check --fix ./fast_pauli/py ./tests lint-write: - ruff format ./fast_pauli/py ./tests \ No newline at end of file + ruff format ./fast_pauli/py ./tests diff --git a/fast_pauli/py/pypauli/operations.py b/fast_pauli/py/pypauli/operations.py index bec1c44..bc97728 100644 --- a/fast_pauli/py/pypauli/operations.py +++ b/fast_pauli/py/pypauli/operations.py @@ -37,7 +37,7 @@ def multiply(self, state: np.ndarray) -> np.ndarray: ---- state: The input state as a numpy array. - Returns: + Returns ------- The result of multiplying the Pauli string with the state. @@ -61,7 +61,7 @@ def compose_sparse_pauli(string: str) -> tuple[np.ndarray, np.ndarray]: string: The input string representing the Pauli string. Must contain only I, X, Y, Z characters. - Returns: + Returns ------- A tuple containing the column numbers and values of the sparse matrix. @@ -120,7 +120,7 @@ def compose_sparse_diag_pauli(string) -> np.ndarray: ---- string: A Pauli string containing only 'I' and 'Z' characters. - Returns: + Returns ------- np.ndarray: diagonal values from resulting sparse matrix. diff --git a/pyproject.toml b/pyproject.toml index b02dbca..2a71fe7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,7 +109,22 @@ target-version="py38" # D - pydocstyle (https://docs.astral.sh/ruff/rules/#pydocstyle-d) # YTT - flake8-2020 (https://docs.astral.sh/ruff/rules/#flake8-2020-ytt) # ASYNC flake8-async (https://docs.astral.sh/ruff/rules/#flake8-async-async) +# B flake8-bugbear (https://docs.astral.sh/ruff/rules/#flake8-bugbear-b) +# UP flake8-bandit (https://docs.astral.sh/ruff/rules/#flake8-bandit-up) +# SIM flake8-simplify (https://docs.astral.sh/ruff/rules/#flake8-simplify-sim) -select = ["F", "E", "W", "I", "N", "D", "YTT", "ASYNC"] +select = ["F", "E", "W", "I", "N", "D", "YTT", "ASYNC", "B", "UP", "SIM"] -ignore = [] +ignore = [ + # Allow non-lowercase variable names to help the code match the math better + "N806", + # mypy doesn't support PEP695 (upgrading to `type` python keyword) + "UP040", +] + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + +[tool.ruff.format] +quote-style = "double" +docstring-code-format = true From 177ecbb583d5f4fae9ebc323b69aceb46febcc40 Mon Sep 17 00:00:00 2001 From: Eugene Rublenko <16805621+stand-by@users.noreply.github.com> Date: Mon, 22 Jul 2024 12:33:57 -0400 Subject: [PATCH 10/11] Pull in some settings for pre-commit and makefile from template repo --- .pre-commit-config.yaml | 21 +++++++++++++++++++++ Makefile | 19 ++++++++++++------- pyproject.toml | 1 + 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 901f2e9..73543af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,6 +8,10 @@ repos: rev: v0.10.0 hooks: - id: yamlfmt + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: trailing-whitespace # C/C++ - repo: https://github.com/pre-commit/mirrors-clang-format rev: v18.1.8 @@ -20,5 +24,22 @@ repos: hooks: # Run the linter. - id: ruff + types_or: [python, pyi, jupyter] + args: ["--config", "pyproject.toml", "--fix", "--show-fixes"] # Run the formatter. - id: ruff-format + types_or: [python, pyi, jupyter] + args: ["--config", "pyproject.toml"] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.9.0 + hooks: + - id: mypy + args: ["--config", "pyproject.toml"] + # Misc + - repo: https://github.com/codespell-project/codespell + rev: v2.2.4 + hooks: + - id: codespell + args: ["--ignore-words-list", "bra,ket"] + additional_dependencies: + - tomli # So we can configure it to use pyproject.toml diff --git a/Makefile b/Makefile index 8bc3d04..1f426f4 100644 --- a/Makefile +++ b/Makefile @@ -19,12 +19,17 @@ tests: clean: rm -rf build dist -lint-check: - ruff check ./fast_pauli/py ./tests && \ - mypy ./fast_pauli/py ./tests +lint: + pre-commit run --all-files -- ruff + pre-commit run --all-files -- mypy + pre-commit run --all-files -- codespell -lint-fix: - ruff check --fix ./fast_pauli/py ./tests +# run with make -i to ignore errors and run all three formatters +format: + pre-commit run --all-files -- ruff-format + pre-commit run --all-files -- yamlfmt + pre-commit run --all-files -- trailing-whitespace -lint-write: - ruff format ./fast_pauli/py ./tests +.PHONY: pre-commit-setup +pre-commit-setup: + pre-commit install diff --git a/pyproject.toml b/pyproject.toml index 2a71fe7..15865c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ warn_return_any = true no_implicit_optional = true strict_optional = true ignore_missing_imports = true +explicit_package_bases = true exclude = [] From b328a92d464ff5ffbd21b3858b921e5f02d90e96 Mon Sep 17 00:00:00 2001 From: Eugene Rublenko <16805621+stand-by@users.noreply.github.com> Date: Mon, 22 Jul 2024 12:35:21 -0400 Subject: [PATCH 11/11] Make code compliant with new linting and formatting rules --- README.md | 2 +- fast_pauli/cpp/include/__pauli_op.hpp | 2 +- fast_pauli/cpp/include/__pauli_string.hpp | 2 +- fast_pauli/cpp/tests/test_pauli_string.cpp | 2 +- fast_pauli/py/pypauli/helpers.py | 2 +- fast_pauli/py/pypauli/operations.py | 2 +- fast_pauli/py/tests/test_operations.py | 20 ++++++++++---------- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 15a5b51..8050733 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ - [X] PauliString - [ ] PauliOp - [ ] SummedPauliOp -- [ ] Figure out the tranpose non-sense or support both (some functions take the transpose of the states and others don't) +- [ ] Figure out the transpose non-sense or support both (some functions take the transpose of the states and others don't) - [ ] Clean up `apply_batch` we shouldn't need to pass a coeff - [ ] Clean up tests - [ ] Add apply method to SummedPauliOp that takes precomputed weighted data diff --git a/fast_pauli/cpp/include/__pauli_op.hpp b/fast_pauli/cpp/include/__pauli_op.hpp index e886f45..df1771a 100644 --- a/fast_pauli/cpp/include/__pauli_op.hpp +++ b/fast_pauli/cpp/include/__pauli_op.hpp @@ -163,7 +163,7 @@ template struct PauliOp { ps.apply_batch(new_states_local, states, c); } - // Do the reduction and tranpose back + // Do the reduction and transpose back #pragma omp for schedule(static) collapse(2) for (size_t i = 0; i < new_states.extent(0); ++i) { for (size_t t = 0; t < new_states.extent(1); ++t) { diff --git a/fast_pauli/cpp/include/__pauli_string.hpp b/fast_pauli/cpp/include/__pauli_string.hpp index ec51d3f..b2f8b88 100644 --- a/fast_pauli/cpp/include/__pauli_string.hpp +++ b/fast_pauli/cpp/include/__pauli_string.hpp @@ -252,7 +252,7 @@ struct PauliString { * columns * * @tparam T The floating point base to use for all the complex numbers - * @param new_states_T The outpus states after applying the PauliString + * @param new_states_T The output states after applying the PauliString * (n_data x n_dim) * @param states_T THe original states to apply the PauliString to (n_data x * n_dim) diff --git a/fast_pauli/cpp/tests/test_pauli_string.cpp b/fast_pauli/cpp/tests/test_pauli_string.cpp index d261bee..5c31721 100644 --- a/fast_pauli/cpp/tests/test_pauli_string.cpp +++ b/fast_pauli/cpp/tests/test_pauli_string.cpp @@ -201,7 +201,7 @@ TEST_CASE("test apply batch") { size_t const n_states = 10; // Testing each of these pauli strings individually - // NOTE: apply_batch takes the tranpose of the states and new_states + // NOTE: apply_batch takes the transpose of the states and new_states for (PauliString ps : {"IXYZ", "YYIX", "XXYIYZ", "IZIXYYZ"}) { size_t const dims = ps.dims(); diff --git a/fast_pauli/py/pypauli/helpers.py b/fast_pauli/py/pypauli/helpers.py index f7dbf1a..5ba51bb 100644 --- a/fast_pauli/py/pypauli/helpers.py +++ b/fast_pauli/py/pypauli/helpers.py @@ -3,7 +3,7 @@ import numpy as np -def pauli_matrices() -> dict: +def pauli_matrices() -> dict[str | int, np.ndarray]: """Provide a dictionary containing the Pauli matrices. The Pauli matrices are a set of 2x2 matrices commonly used in quantum mechanics. diff --git a/fast_pauli/py/pypauli/operations.py b/fast_pauli/py/pypauli/operations.py index bc97728..49010fa 100644 --- a/fast_pauli/py/pypauli/operations.py +++ b/fast_pauli/py/pypauli/operations.py @@ -113,7 +113,7 @@ def compose_sparse_pauli(string: str) -> tuple[np.ndarray, np.ndarray]: return cols, vals -def compose_sparse_diag_pauli(string) -> np.ndarray: +def compose_sparse_diag_pauli(string: str) -> np.ndarray: """Produce sparse representation of diagonal pauli string. Args: diff --git a/fast_pauli/py/tests/test_operations.py b/fast_pauli/py/tests/test_operations.py index 07e5343..a790fa8 100644 --- a/fast_pauli/py/tests/test_operations.py +++ b/fast_pauli/py/tests/test_operations.py @@ -13,12 +13,12 @@ @pytest.fixture -def paulis(): +def paulis() -> dict[str | int, np.ndarray]: """Fixture to provide dict with Pauli matrices.""" - return pauli_matrices() + return pauli_matrices() # type: ignore -def test_pauli_string(paulis): +def test_pauli_string(paulis: dict) -> None: """Test the PauliString class.""" for p in ["I", "X", "Y", "Z"]: ps = PauliString(p) @@ -60,7 +60,7 @@ def test_pauli_string(paulis): assert PauliString("ZIXIZYXXY").weight == 7 -def test_sparse_pauli_composer(paulis): +def test_sparse_pauli_composer(paulis: dict) -> None: """Test sparsepauli composer functions.""" ps = PauliString("II") assert ps.dim == 4 @@ -104,7 +104,7 @@ def test_sparse_pauli_composer(paulis): ) -def test_sparse_pauli_composer_equivalence(): +def test_sparse_pauli_composer_equivalence() -> None: """Test the equivalence of sparse Pauli composer with naive method.""" for c in ["I", "X", "Y", "Z"]: np.testing.assert_array_equal( @@ -112,15 +112,15 @@ def test_sparse_pauli_composer_equivalence(): naive_pauli_converter(c), ) - for s in permutations("XYZ", 2): - s = "".join(s) + for p2 in permutations("XYZ", 2): + s = "".join(p2) np.testing.assert_array_equal( PauliString(s).dense(), naive_pauli_converter(s), ) - for s in permutations("IXYZ", 3): - s = "".join(s) + for p3 in permutations("IXYZ", 3): + s = "".join(p3) np.testing.assert_array_equal( PauliString(s).dense(), naive_pauli_converter(s), @@ -138,7 +138,7 @@ def test_sparse_pauli_composer_equivalence(): np.testing.assert_array_equal(PauliString(s).dense(), naive_pauli_converter(s)) -def test_sparse_pauli_multiply(): +def test_sparse_pauli_multiply() -> None: """Test the equivalence of multiply method that relies on sparse Pauli composer.""" rng = np.random.default_rng(321)