Quantum Objects
###############

The primary user-facing type is :obj:`~qutip.Qobj`, which all users will have
interacted with as it represents any quantum object in QuTiP.
:obj:`~qutip.Qobj` consists of two major components: the underlying data
representation of the object, and information about what object is being
represented.  In fact, :obj:`~qutip.Qobj` itself is only really responsible for
managing this "auxiliary" information, ensuring that all mathematical operations
on the data make sense, and neatly wrapping the data-layer functions to be more
convenient for the user.  All the numeric heavy lifting is handled by the data
layer.


Data Store
==========

The data is actually stored in the :attr:`~qutip.Qobj.data` attribute of a
:obj:`~qutip.Qobj`.  This is always a "data-layer type", which will be covered
in more detail in the coming sections.  The different available types store the
data in different manners, such as full two-dimensional dense matrices (like
NumPy arrays) or compressed sparse matrix formats, as different formats have
different advantages.  :obj:`~qutip.Qobj` in general does not care what form its
data is stored in, as all numerical operations are handed off to the data-layer
dispatchers, which will ensure the correct specialised methods are called.

Typically these data layer types do not expose buffers to Python-space
programmes directly, though individual types may have an :meth:`as_array`,
:meth:`as_scipy`, or similar method.  This is for speed and memory-safety.  Once
Python can access the underlying data buffers directly, all references to them
must be managed by the garbage collector to ensure that nothing goes stale, even
if only partial slices onto the data are taken.  To access a copy of the data,
:obj:`~qutip.Qobj` exposes the :meth:`~qutip.Qobj.full` method, which will
always return a two-dimensional :obj:`numpy.ndarray`.  If your object is too
large to reasonably have a dense-matrix representation, you will need to use the
specific methods on the data-layer type that is used.

The amount of information guaranteed to be stored by data-layer types is very
small---sufficient to know whether a linear algebra operation *can* take place.
This is currently just the shape of the data being represented; there is no
information on Hilbert space (tensor product) dimensions, what type of object
the data represents, or anything else.  This is all managed by
:obj:`~qutip.Qobj`.


Auxiliary Information
=====================

:obj:`~qutip.Qobj` contains several pieces of auxiliary information about the
object being represented.  The major parts are:

:attr:`qutip.Qobj.dims`
   The tensor-product structure that this quantum object lives in.  This is a
   list of two elements---the "left" dimensions and the "right" dimensions.  For
   example, an operator on a Hilbert space formed of two qubits has
   :attr:`~qutip.Qobj.dims` of ``[[2, 2], [2, 2]]``.  A state (a "ket") in the
   same space has dimensions of ``[[2, 2], [1, 1]]``.  The data structure
   storing these two objects would not keep this information; the
   :attr:`~qutip.core.data.Data.shape` attributes would respectively be simply
   ``(4, 4)`` and ``(4, 1)``.

:attr:`qutip.Qobj.type`
   The type of object represented by this :obj:`~qutip.Qobj`.  This is typically
   derived from :attr:`~qutip.Qobj.dims`.  This is a string, containing a
   human-readable description of what type the object is, such as ``"oper"``,
   ``"ket"`` or ``"bra"``.

:attr:`qutip.Qobj.superrep`
   If this object is a super-operator, then this attribute is a string
   describing the particular representation used, such as the default
   ``"super"`` or something more specific like ``"choi"``.


Methods
=======

As :obj:`~qutip.Qobj` is the primary Python user-facing type, it provides all
the Python niceties and "magic" methods, such as :meth:`~qutip.Qobj.__add__`,
:meth:`~qutip.Qobj.__mul__` and so forth.  :obj:`~qutip.Qobj` will check that
the operation makes sense, returning :data:`NotImplemented` on failure, but a
successful output will typically require passing off to the data layer.  The
data layer types themselves generally do *not* have these magic methods, as they
are not designed to be manipulated by user code in Python.

In addition to the operator-overloading methods, :obj:`~qutip.Qobj` also
provides many quantum-specific mathematical operations, such as
:meth:`~qutip.Qobj.dag`, :meth:`~qutip.Qobj.norm`, :meth:`~qutip.Qobj.proj` and
:meth:`~qutip.Qobj.eigenstates`.  Typically these are pass-throughs to the data
layer, as :obj:`~qutip.Qobj` accesses its own data using the abstract interface
defined there.

:obj:`~qutip.Qobj` adds in its own :meth:`~qutip.Qobj.__repr__` methods for nice
output in REPLs, and includes IPython and Jupyter integration.  When running in
a rich environment, the :meth:`~qutip.Qobj._repr_latex_` method will be called
to better format the output.


Derived and Related Types
=========================

:obj:`~qutip.Qobj` is not the only type representing quantum objects in QuTiP,
but it is by far the most common from the user's perspective.  Sometimes, for
efficiency, other classes are necessary which either derive from or wrap
:obj:`~qutip.Qobj`.  These are often more specialised, and used only in some
parts of the code base.


QobjEvo
-------

The most wide-spread of these related types is :obj:`~qutip.QobjEvo`, which
represents a time-dependent quantum objects.  Essentially, it represents an
object :math:`A(t)` which can be described by

.. math::
   A(t) = \sum_k f_k(t) A_k

for some scalar, time-dependent functions :math:`f_k(t)` and some time-\
*independent* quantum objects in compatible Hilbert spaces :math:`A_k`.  These
:math:`A_k` will be instances of :obj:`~qutip.Qobj`.

This class is mostly for usage in solvers and optimisers which work on
time-dependent objects, as it can transpile the scalar time-dependence down to
C code and compile this to run natively.  The classes may be instantiated
directly by users when they want to reuse the results of compilation, as this
process can takes a few seconds.
