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.