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.