Skip to content

Commit

Permalink
Merge pull request #1428 from qiboteam/doc_hamiltonian
Browse files Browse the repository at this point in the history
Improved docs of `Hamiltonian` and `Gradients` modules
  • Loading branch information
renatomello authored Aug 30, 2024
2 parents 21732ad + 5b26fe6 commit f20f433
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 109 deletions.
6 changes: 3 additions & 3 deletions doc/source/api-reference/qibo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1213,9 +1213,9 @@ Matrix Hamiltonian
^^^^^^^^^^^^^^^^^^

The first implementation of Hamiltonians uses the full matrix representation
of the Hamiltonian operator in the computational basis. This matrix has size
``(2 ** nqubits, 2 ** nqubits)`` and therefore its construction is feasible
only when number of qubits is small.
of the Hamiltonian operator in the computational basis.
For :math:`n` qubits, this matrix has size :math:`2^{n} \times 2^{n}`.
Therefore, its construction is feasible only when :math:`n` is small.

Alternatively, the user can construct this Hamiltonian using a sparse matrices.
Sparse matrices from the
Expand Down
49 changes: 26 additions & 23 deletions src/qibo/derivative.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,43 @@ def parameter_shift(
nshots=None,
):
"""In this method the parameter shift rule (PSR) is implemented.
Given a circuit U and an observable H, the PSR allows to calculate the derivative
of the expected value of H on the final state with respect to a variational
Given a circuit :math:`U` and an observable :math:`H`, the PSR allows to calculate the derivative
of the expected value of :math:`H` on the final state with respect to a variational
parameter of the circuit.
There is also the possibility of setting a scale factor. It is useful when a
circuit's parameter is obtained by combination of a variational
parameter and an external object, such as a training variable in a Quantum
Machine Learning problem. For example, performing a re-uploading strategy
to embed some data into a circuit, we apply to the quantum state rotations
whose angles are in the form: theta' = theta * x, where theta is a variational
parameter and x an input variable. The PSR allows to calculate the derivative
with respect of theta' but, if we want to optimize a system with respect its
variational parameters we need to "free" this procedure from the x depencency.
If the `scale_factor` is not provided, it is set equal to one and doesn't
whose angles are in the form :math:`\\theta^{\\prime} = x \\, \\theta`,
where :math:`\\theta` is a variational parameter, and :math:`x` an input variable.
The PSR allows to calculate the derivative with respect to :math:`\\theta^{\\prime}`.
However, if we want to optimize a system with respect to its
variational parameters, we need to "free" this procedure from the :math:`x` depencency.
If the ``scale_factor`` is not provided, it is set equal to one and doesn't
affect the calculation.
If the PSR is needed to be executed on a real quantum device, it is important
to set `nshots` to some integer value. This enables the execution on the
to set ``nshots`` to some integer value. This enables the execution on the
hardware by calling the proper methods.
Args:
circuit (:class:`qibo.models.circuit.Circuit`): custom quantum circuit.
hamiltonian (:class:`qibo.hamiltonians.Hamiltonian`): target observable.
if you want to execute on hardware, a symbolic hamiltonian must be
provided as follows (example with Pauli Z and ``nqubits=1``):
provided as follows (example with Pauli-:math:`Z` and :math:`n = 1`):
``SymbolicHamiltonian(np.prod([ Z(i) for i in range(1) ]))``.
parameter_index (int): the index which identifies the target parameter
in the ``circuit.get_parameters()`` list.
initial_state (ndarray, optional): initial state on which the circuit
acts. Default is ``None``.
scale_factor (float, optional): parameter scale factor. Default is ``1``.
acts. If ``None``, defaults to the zero state :math:`\\ket{\\mathbf{0}}`.
Defaults to ``None``.
scale_factor (float, optional): parameter scale factor. Defaults to :math:`1`.
nshots (int, optional): number of shots if derivative is evaluated on
hardware. If ``None``, the simulation mode is executed.
Default is ``None``.
Defaults to ``None``.
Returns:
(float): Value of the derivative of the expectation value of the hamiltonian
float: Value of the derivative of the expectation value of the hamiltonian
with respect to the target variational parameter.
Example:
Expand Down Expand Up @@ -167,27 +169,28 @@ def finite_differences(
step_size=1e-7,
):
"""
Calculate derivative of the expectation value of `hamiltonian` on the
final state obtained by executing `circuit` on `initial_state` with
respect to the variational parameter identified by `parameter_index`
Calculate derivative of the expectation value of ``hamiltonian`` on the
final state obtained by executing ``circuit`` on ``initial_state`` with
respect to the variational parameter identified by ``parameter_index``
in the circuit's parameters list. This method can be used only in
exact simulation mode.
Args:
circuit (:class:`qibo.models.circuit.Circuit`): custom quantum circuit.
hamiltonian (:class:`qibo.hamiltonians.Hamiltonian`): target observable.
if you want to execute on hardware, a symbolic hamiltonian must be
provided as follows (example with Pauli Z and ``nqubits=1``):
To execute on hardware, a symbolic hamiltonian must be
provided as follows (example with Pauli-:math:`Z` and :math:`n = 1`):
``SymbolicHamiltonian(np.prod([ Z(i) for i in range(1) ]))``.
parameter_index (int): the index which identifies the target parameter
in the ``circuit.get_parameters()`` list.
in the :meth:`qibo.models.Circuit.get_parameters` list.
initial_state (ndarray, optional): initial state on which the circuit
acts. Default is ``None``.
step_size (float): step size used to evaluate the finite difference
(default 1e-7).
acts. If ``None``, defaults to the zero state :math:`\\ket{\\mathbf{0}}`.
Defaults to ``None``.
step_size (float, optional): step size used to evaluate the finite difference.
Defaults to :math:`10^{-7}`.
Returns:
(float): Value of the derivative of the expectation value of the hamiltonian
float: Value of the derivative of the expectation value of the hamiltonian
with respect to the target variational parameter.
"""

Expand Down
27 changes: 15 additions & 12 deletions src/qibo/hamiltonians/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,10 @@ def ground_state(self):

@abstractmethod
def exp(self, a): # pragma: no cover
"""Computes a tensor corresponding to exp(-1j * a * H).
"""Computes a tensor corresponding to :math:`\\exp(-i \\, a \\, H)`.
Args:
a (complex): Complex number to multiply Hamiltonian before
exponentiation.
a (complex): Complex number to multiply Hamiltonian before exponentiation.
"""
raise_error(NotImplementedError)

Expand All @@ -70,27 +69,31 @@ def expectation(self, state, normalize=False): # pragma: no cover
"""Computes the real expectation value for a given state.
Args:
state (array): the expectation state.
normalize (bool): If ``True`` the expectation value is divided
with the state's norm squared.
state (ndarray): state in which to calculate the expectation value.
normalize (bool, optional): If ``True``, the expectation value
:math:`\\ell_{2}`-normalized. Defaults to ``False``.
Returns:
Real number corresponding to the expectation value.
float: real number corresponding to the expectation value.
"""
raise_error(NotImplementedError)

@abstractmethod
def expectation_from_samples(self, freq, qubit_map=None): # pragma: no cover
"""Computes the real expectation value of a diagonal observable given the frequencies when measuring in the computational basis.
"""Computes the expectation value of a diagonal observable,
given computational-basis measurement frequencies.
Args:
freq (collections.Counter): the keys are the observed values in binary form
and the values the corresponding frequencies, that is the number
of times each measured value/bitstring appears.
qubit_map (tuple): Mapping between frequencies and qubits. If None, [1,...,len(key)]
and the values the corresponding frequencies, that is the number
of times each measured value/bitstring appears.
qubit_map (tuple): Mapping between frequencies and qubits.
If ``None``, then defaults to
:math:`[1, \\, 2, \\, \\cdots, \\, \\mathrm{len}(\\mathrm{key})]`.
Defaults to ``None``.
Returns:
Real number corresponding to the expectation value.
float: real number corresponding to the expectation value.
"""
raise_error(NotImplementedError)

Expand Down
61 changes: 34 additions & 27 deletions src/qibo/hamiltonians/hamiltonians.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ class Hamiltonian(AbstractHamiltonian):
Args:
nqubits (int): number of quantum bits.
matrix (np.ndarray): Matrix representation of the Hamiltonian in the
computational basis as an array of shape ``(2 ** nqubits, 2 ** nqubits)``.
Sparse matrices based on ``scipy.sparse`` for numpy/qibojit backends
or on ``tf.sparse`` for the tensorflow backend are also
supported.
matrix (ndarray): Matrix representation of the Hamiltonian in the
computational basis as an array of shape :math:`2^{n} \\times 2^{n}`.
Sparse matrices based on ``scipy.sparse`` for ``numpy`` / ``qibojit`` backends
(or on ``tf.sparse`` for the ``tensorflow`` backend) are also supported.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses :class:`qibo.backends.GlobalBackend`.
Defaults to ``None``.
"""

def __init__(self, nqubits, matrix=None, backend=None):
def __init__(self, nqubits, matrix, backend=None):
from qibo.backends import _check_backend

self.backend = _check_backend(backend)
Expand All @@ -50,7 +52,7 @@ def __init__(self, nqubits, matrix=None, backend=None):
def matrix(self):
"""Returns the full matrix representation.
Can be a dense ``(2 ** nqubits, 2 ** nqubits)`` array or a sparse
For :math:`n` qubits, can be a dense :math:`2^{n} \\times 2^{n}` array or a sparse
matrix, depending on how the Hamiltonian was created.
"""
return self._matrix
Expand All @@ -68,22 +70,22 @@ def matrix(self, mat):

@classmethod
def from_symbolic(cls, symbolic_hamiltonian, symbol_map, backend=None):
"""Creates a ``Hamiltonian`` from a symbolic Hamiltonian.
"""Creates a :class:`qibo.hamiltonian.Hamiltonian` from a symbolic Hamiltonian.
We refer to the
:ref:`How to define custom Hamiltonians using symbols? <symbolicham-example>`
example for more details.
We refer to :ref:`How to define custom Hamiltonians using symbols? <symbolicham-example>`
for more details.
Args:
symbolic_hamiltonian (sympy.Expr): The full Hamiltonian written
with symbols.
symbolic_hamiltonian (sympy.Expr): full Hamiltonian written with ``sympy`` symbols.
symbol_map (dict): Dictionary that maps each symbol that appears in
the Hamiltonian to a pair of (target, matrix).
the Hamiltonian to a pair ``(target, matrix)``.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses :class:`qibo.backends.GlobalBackend`.
Defaults to ``None``.
Returns:
A :class:`qibo.hamiltonians.SymbolicHamiltonian` object
that implements the Hamiltonian represented by the given symbolic
expression.
:class:`qibo.hamiltonians.SymbolicHamiltonian`: object that implements the
Hamiltonian represented by the given symbolic expression.
"""
log.warning(
"`Hamiltonian.from_symbolic` and the use of symbol maps is "
Expand Down Expand Up @@ -175,15 +177,16 @@ def energy_fluctuation(self, state):
Evaluate energy fluctuation:
.. math::
\\Xi_{k}(\\mu) = \\sqrt{\\langle\\mu|\\hat{H}^2|\\mu\\rangle - \\langle\\mu|\\hat{H}|\\mu\\rangle^2} \\,
\\Xi_{k}(\\mu) = \\sqrt{\\bra{\\mu} \\, H^{2} \\, \\ket{\\mu}
- \\bra{\\mu} \\, H \\, \\ket{\\mu}^2} \\, .
for a given state :math:`|\\mu\\rangle`.
for a given state :math:`\\ket{\\mu}`.
Args:
state (np.ndarray): quantum state to be used to compute the energy fluctuation.
state (ndarray): quantum state to be used to compute the energy fluctuation.
Return:
Energy fluctuation value (float).
Returns:
float: Energy fluctuation value.
"""
state = self.backend.cast(state)
energy = self.expectation(state)
Expand Down Expand Up @@ -311,6 +314,9 @@ class SymbolicHamiltonian(AbstractHamiltonian):
The symbol_map can also be used to pass non-quantum operator arguments
to the symbolic Hamiltonian, such as the parameters in the
:meth:`qibo.hamiltonians.models.MaxCut` Hamiltonian.
backend (:class:`qibo.backends.abstract.Backend`, optional): backend to be used
in the execution. If ``None``, it uses :class:`qibo.backends.GlobalBackend`.
Defaults to ``None``.
"""

def __init__(self, form=None, nqubits=None, symbol_map={}, backend=None):
Expand Down Expand Up @@ -419,7 +425,7 @@ def terms(self, terms):
def matrix(self):
"""Returns the full matrix representation.
Consisting of ``(2 ** nqubits, 2 ** nqubits)`` elements.
Consisting of :math:`2^{n} \\times 2^{n}`` elements.
"""
return self.dense.matrix

Expand Down Expand Up @@ -449,8 +455,8 @@ def _get_symbol_matrix(self, term):
term (sympy.Expr): Symbolic expression containing local operators.
Returns:
Numerical matrix corresponding to the given expression as a numpy
array of size ``(2 ** self.nqubits, 2 ** self.nqubits).
ndarray: matrix corresponding to the given expression as an array
of shape ``(2 ** self.nqubits, 2 ** self.nqubits)``.
"""
if isinstance(term, sympy.Add):
# symbolic op for addition
Expand Down Expand Up @@ -697,7 +703,7 @@ def apply_gates(self, state, density_matrix=False):
Gates are applied to the given state.
Helper method for ``__matmul__``.
Helper method for :meth:`qibo.hamiltonians.SymbolicHamiltonian.__matmul__`.
"""
total = 0
for term in self.terms:
Expand Down Expand Up @@ -759,7 +765,8 @@ def circuit(self, dt, accelerators=None):
Args:
dt (float): Time step used for Trotterization.
accelerators (dict): Dictionary with accelerators for distributed circuits.
accelerators (dict, optional): Dictionary with accelerators for distributed circuits.
Defaults to ``None``.
"""
from qibo import Circuit # pylint: disable=import-outside-toplevel
from qibo.hamiltonians.terms import ( # pylint: disable=import-outside-toplevel
Expand Down
Loading

0 comments on commit f20f433

Please sign in to comment.