diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml
index 1ca5496..2080915 100644
--- a/.github/actions/build/action.yml
+++ b/.github/actions/build/action.yml
@@ -7,7 +7,7 @@ runs:
run: python3 ./.github/compiler_setup.py ${CXX} ${BUILD_OS}
- name: Install Python dependencies
shell: bash
- run: python -m pip install cmake scikit-build-core
+ run: python -m pip install cmake scikit-build-core setuptools_scm build
- name: Build
shell: bash
run: |
@@ -24,5 +24,4 @@ runs:
- name: Build Publishable release distributions for PyPI
shell: bash
run: |
- python -m pip install build
python -m build -s
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2f21a1e..7c6df98 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -7,7 +7,7 @@ on:
permissions:
id-token: write # This is required for requesting the JWT
jobs:
- main-build:
+ source_dist_build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
@@ -28,5 +28,23 @@ jobs:
env:
BUILD_OS: ${{ matrix.os }}
CXX: ${{ matrix.compiler }}
- - name: Publish release distributions to PyPI
+ - name: Publish source dist release distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
+ wheels_build:
+ name: Build wheels on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ # macos-13 is an intel runner, macos-14 is apple silicon
+ # os: [ubuntu-latest, macos-13, macos-14]
+ os: [ubuntu-latest]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Build wheels
+ uses: pypa/cibuildwheel@v2.21.2
+ with:
+ config-file: "{package}/pyproject.toml"
+ - name: Publish wheel release distributions to PyPI
+ uses: pypa/gh-action-pypi-publish@release/v1
+ with:
+ packages-dir: wheelhouse
diff --git a/CMakeLists.txt b/CMakeLists.txt
index aad11ec..18d1243 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -74,7 +74,7 @@ find_package(OpenMP REQUIRED)
# Setup Python
find_package(Python 3.10 # This is a minimum version
- COMPONENTS Interpreter Development REQUIRED)
+ COMPONENTS Interpreter Development.Module REQUIRED)
# Our primary target
add_library(fast_pauli INTERFACE)
diff --git a/Makefile b/Makefile
index a20403c..3fd31aa 100644
--- a/Makefile
+++ b/Makefile
@@ -99,7 +99,7 @@ format:
.PHONY: clean
clean:
- rm -rf build dist
+ rm -rf build dist wheelhouse
.PHONY: pre-commit-setup
pre-commit-setup:
diff --git a/docs/getting_started.rst b/docs/getting_started.rst
index fa9a7b0..886d6df 100644
--- a/docs/getting_started.rst
+++ b/docs/getting_started.rst
@@ -3,17 +3,33 @@
Getting Started
=====================
-Welcome to Fast-Pauli from `Qognitive `_, an open-source Python / C++ library for optimized operations on Pauli matrices and Pauli strings
-based on `PauliComposer `_. In this guide,
-we'll introduce some of the important operations to help users get started. For more details,
-see the API documentation.
+Welcome to :code:`fast-pauli` from `Qognitive `_, an open-source Python / C++ library for optimized operations on Pauli matrices and Pauli strings
+based on `PauliComposer `_.
+In this guide, we'll introduce some of the important operations to help users get started as well as some conceptual background on Pauli matrices and Pauli strings.
-For tips on installing the library, check out the guide: :doc:`index`.
+If you want to follow along with this guide, please follow the installation instructions in :doc:`index`.
+For more details on our programmatic interface, see the :doc:`python_api` or :doc:`cpp_api` documentation.
Pauli Matrices
------------------------
-For a conceptual overview of Pauli matrices, see `here `_.
+For a more in-depth overview of Pauli matrices, see `here `_.
+
+In math and physics, a `Pauli matrix `_, named after the physicist Wolfgang Pauli, is any one of the special 2 x 2 complex matrices in the set (often denoted by the greek letter :math:`\sigma`) :
+
+.. math::
+
+ \sigma_x = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}
+ \sigma_y = \begin{bmatrix} 0 & -i \\ i & 0 \end{bmatrix}
+ \sigma_z = \begin{bmatrix} 1 & 0 \\ 0 & -1 \end{bmatrix}
+
+All the Pauli matrices share the properties that they are:
+
+1. Hermitian (equal to their own conjugate transpose) :math:`\sigma_i = \sigma_i^\dagger` for all :math:`i \in \{x, y, z\}`
+2. Involutory (they are their own inverse) :math:`\sigma_i^2 = \sigma_0` for all :math:`i \in \{x, y, z\}`
+3. Unitary (their inverse is equal to their conjugate transpose) :math:`\sigma_i^{-1} = \sigma_i^\dagger` for all :math:`i \in \{x, y, z\}`
+
+with the identity matrix :math:`\sigma_0` or the :math:`2 \times 2` Identity matrix :math:`I` being the trivial case.
In ``fast_pauli``, we represent pauli matrices using the ``Pauli`` class. For example, to represent the Pauli matrices, we can do:
@@ -26,13 +42,17 @@ In ``fast_pauli``, we represent pauli matrices using the ``Pauli`` class. For ex
pauli_y = fp.Pauli('Y')
pauli_z = fp.Pauli('Z')
-We can also multiply two ``Pauli`` objects together to get a ``Pauli`` object representing the tensor product of the two pauli matrices.
+ str(pauli_0) # returns "I"
+
+We can also multiply two ``Pauli`` objects together to get a ``Pauli`` object representing the matrix product of the two pauli matrices.
+The result includes a phase factor because the product of two Pauli matrices is not another Pauli matrix, but rather a Pauli matrix multiplied by either ``i`` or ``-i`` which we call a phase factor.
+
+For example, we can compute the resulting ``Pauli`` object from multiplying :math:`\sigma_x` and :math:`\sigma_y` as follows:
.. code-block:: python
+ # phase = i, new_pauli = fp.Pauli('Z')
phase, new_pauli = pauli_x @ pauli_y
- # returns "I"
- str(pauli_0)
From here, we can also convert our ``Pauli`` object back to a dense numpy array if we'd like:
@@ -43,7 +63,23 @@ From here, we can also convert our ``Pauli`` object back to a dense numpy array
Pauli Strings
------------------------
-In ``fast_pauli``, we represent Pauli strings using the ``PauliString`` class. For example, to construct the Pauli string ``X, Y, Z``, we can do:
+Pauli strings are tensor-product combinations of Pauli matrices. For example, the following is a valid Pauli string:
+
+.. math::
+
+ \mathcal{\hat{P}} = \sigma_x \otimes \sigma_y \otimes \sigma_z
+
+where :math:`\otimes` denotes the tensor or `Kronecker `_ product, and we can more simply denote by
+
+.. math::
+
+ \mathcal{\hat{P}} = XYZ
+
+A Pauli string of length ``N`` is a tensor product of ``N``
+Pauli matrices. We can represent a ``N``-length Pauli String as a :math:`2^N \times 2^N` operator or matrix (in physics, an operator is represented by a matrix in a given basis), so ``XYZ`` is a :math:`8 \times 8` matrix.
+Since these operators can get large very quickly, ``fast_pauli`` represents Pauli strings sparsely as an array of ``Pauli`` objects.
+
+For example, to construct the Pauli string ``XYZ``, we can do:
.. code-block:: python
@@ -61,19 +97,17 @@ Pauli Strings also support operations like addition, multiplication, and more. F
P1.dim
P1.n_qubits
- # Add two Pauli strings. Return type is a PauliOp because
- # the product is not a Pauli string
- P3 = P1 + P2
-
# Multiply two Pauli strings.
phase, new_string = P1 @ P2
-We can also do more complicated things, like compute the action of a Pauli string :math:`\mathcal{\hat{P}}` on a quantum state :math:`| \psi \rangle`, :math:`\mathcal{\hat{P}}| \psi \rangle`, or
-compute the expectation value of a Pauli string with a state :math:`\langle \psi | \mathcal{\hat{P}} | \psi \rangle`:
+We can also do more complicated things, like compute the action of a Pauli string :math:`\mathcal{\hat{P}}` on a vector :math:`| \psi \rangle`, :math:`\mathcal{\hat{P}}| \psi \rangle`, or
+compute the expectation value of a Pauli string with a state :math:`\langle \psi | \mathcal{\hat{P}} | \psi \rangle`. As a side note, in this guide we will use state and vector interchangeably:
.. code-block:: python
+ import numpy as np
+
# Apply P to a state
P = fp.PauliString('XY')
state = np.array([1, 0, 0, 1], dtype=complex)
@@ -82,7 +116,7 @@ compute the expectation value of a Pauli string with a state :math:`\langle \psi
# Compute the expected value of P with respect to a state or a batch of states
value = P.expectation_value(state)
- states = np.random.randn(8, 8) + 1j * np.random.randn(8, 8)
+ states = np.random.randn(4, 8) + 1j * np.random.randn(4, 8)
values = P.expectation_value(states)
We can also convert ``PauliString`` objects back to dense numpy arrays if we'd like, or extract their string representation:
@@ -91,16 +125,16 @@ We can also convert ``PauliString`` objects back to dense numpy arrays if we'd l
P = fp.PauliString('XYZ')
P_np = P.to_tensor()
- # Returns "XYZ"
- P_str = str(P)
-For more details on the ``PauliString`` class, see the Python or C++ API documentation.
+ P_str = str(P) # Returns "XYZ"
+
+For more details on the ``PauliString`` class, see the :doc:`python_api` or :doc:`cpp_api` documentation.
Pauli Operators
------------------------
-The ``PauliOp`` class lets us represent operators that are linear combinations of Pauli strings with complex coefficients. More specifically,
-we can represent an arbitrary operator :math:`A` as a sum of Pauli strings :math:`P_i` with complex coefficients :math:`c_i`:
+The ``PauliOp`` class lets us represent operators that are linear combinations of Pauli strings with complex coefficients.
+For example, we can represent any arbitrary operator :math:`A` as a sum of Pauli strings :math:`P_i` with complex coefficients :math:`c_i`:
.. math::
@@ -111,8 +145,8 @@ that represents the operator :math:`A = 0.5 * XYZ + 0.5 * YYZ`, we can do:
.. code-block:: python
- coeffs = np.array([0.5, 0.5], dtype=complex)
- pauli_strings = ['XYZ', 'YYZ']
+ coeffs = np.array([0.5, 0.5], dtype=complex) # represent c_i in the sum above
+ pauli_strings = ['XYZ', 'YYZ'] # represent P_i in the sum above
A = fp.PauliOp(coeffs, pauli_strings)
# Get the number of qubits the operator acts on,
@@ -122,8 +156,8 @@ that represents the operator :math:`A = 0.5 * XYZ + 0.5 * YYZ`, we can do:
A.dim
A.n_pauli_strings
-Just like with ``PauliString`` objects, we can apply ``PauliOp`` objects to a set of quantum states or compute expectation values, as well as arithmetic
-operations and dense matrix conversions. Just like with ``PauliString`` objects, we can also convert ``PauliOp`` objects back to dense numpy arrays if we'd like
+Just like with ``PauliString`` objects, we can apply ``PauliOp`` objects to a set of vectors, or compute expectation values, as well as arithmetic
+operations. Just like with ``PauliString`` objects, we can also convert ``PauliOp`` objects back to dense numpy arrays if we'd like
or get their string representation, in this case a list of strings:
.. code-block:: python
@@ -132,6 +166,13 @@ or get their string representation, in this case a list of strings:
pauli_strings = ['XYZ', 'YYZ']
A = fp.PauliOp(coeffs, pauli_strings)
+ # Adding two Pauli strings returns a PauliOp.
+ # The returned object is a PauliOp because
+ # the sum is a linear combination of Pauli strings
+ P1 = fp.PauliString('XYZ')
+ P2 = fp.PauliString('YZX')
+ O = P1 + P2
+
# PauliOp supports addition, subtraction, multiplication,
# scaling, as well as have PauliString objects
# as the second operand. All valid operations:
@@ -141,8 +182,8 @@ or get their string representation, in this case a list of strings:
s = fp.PauliString('XYZ')
A4 = A1 + s
- # Apply A to a state or set of states
- states = np.random.rand(10, 8) + 1j * np.random.rand(10, 8)
+ # Apply A to a single state / vector or set
+ states = np.random.rand(8, 10) + 1j * np.random.rand(8, 10)
new_states = A.apply(states)
# Compute the expectation value of A with respect to a state
@@ -156,19 +197,19 @@ or get their string representation, in this case a list of strings:
Qiskit Integration
------------------------
-``Fast-Pauli`` also has integration with `IBM's Qiskit SDK `_, allowing for easy interfacing with certain Qiskit objects. For example, we can convert
+``fast_pauli`` also has integration with `IBM's Qiskit SDK `_, allowing for easy interfacing with certain Qiskit objects. For example, we can convert
between ``PauliOp`` objects and ``SparsePauliOp`` objects from Qiskit:
.. code-block:: python
- # Convert a Fast-Pauli PauliOp to a Qiskit SparsePauliOp object and back
+ # Convert a fast_pauli PauliOp to a Qiskit SparsePauliOp object and back
O = fp.PauliOp([1], ['XYZ'])
qiskit_op = fp.to_qiskit(O)
fast_pauli_op = fp.from_qiskit(qiskit_op)
- # Convert a Fast-Pauli PauliString to a Qiskit Pauli object
+ # Convert a fast_pauli PauliString to a Qiskit Pauli object
P = fp.PauliString('XYZ')
qiskit_pauli = fp.to_qiskit(P)
-For more details on Qiskit conversions, see the Python or C++ API documentation.
+For more details on Qiskit conversions, see the :doc:`python_api` documentation.
diff --git a/pyproject.toml b/pyproject.toml
index 8ea1ba8..ae8ba40 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,6 +21,7 @@
requires = [
# scikit-build-core uses cmake as needed (do not list it here)
"scikit-build-core",
+ "setuptools_scm[toml]>=8",
]
build-backend = "scikit_build_core.build"
@@ -41,17 +42,19 @@ authors = [
{ name="Jeffrey Berger", email="jeff.berger@qognitive.io" },
]
dependencies = ["numpy", "qiskit", "qiskit-algorithms"]
-version = "0.0.8"
+requires-python = ">= 3.10"
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
+dynamic = ["version"]
[project.optional-dependencies]
dev = [
+ "cibuildwheel",
"clang-format",
"cmake-format",
"pre-commit",
@@ -59,6 +62,7 @@ dev = [
"ruff",
"pytest",
"pytest-benchmark",
+ "setuptools_scm",
"sphinx",
"sphinx_rtd_theme",
"sphinx-autobuild",
@@ -178,5 +182,16 @@ docstring-code-format = true
# pip install -e . --verbose -C cmake.args="-DCMAKE_CXX_COMPILER=clang++-18"
[tool.scikit-build]
cmake.build-type = "Release"
-build-dir = "build"
+build-dir = "build/{wheel_tag}"
+metadata.version.provider = "scikit_build_core.metadata.setuptools_scm"
+sdist.include = ["fast_pauli/__version__.py"]
# TODO add more options here
+
+[tool.setuptools_scm]
+write_to = "fast_pauli/__version__.py"
+
+[tool.cibuildwheel]
+build = ["cp310-manylinux_x86_64", "cp311-manylinux_x86_64", "cp312-manylinux_x86_64", "cp313-manylinux_x86_64"]
+build-frontend = "build"
+test-requires = "pytest"
+test-command = "pytest -s -vv --import-mode importlib {project}/tests/fast_pauli"