*******************************
Internal representation details
*******************************

..
    FIXME: This chapter is very much outdated.
    Most of the concepts are still the same but
    a lot of the details are different.


This chapter explains how UFL forms and expressions are represented
in detail. Most operations are mirrored by a representation class,
e.g., ``Sum`` and ``Product``, which are subclasses
of ``Expr``.  You can import all of them from the submodule
``ufl_legacy.classes`` by

::

  from ufl_legacy.classes import *

Structure of a form
===================

Each ``Form`` owns multiple ``Integral`` instances, each associated
with a different ``Measure``.  An ``Integral`` owns a ``Measure``
and an ``Expr``, which represents the integrand expression. The
``Expr`` is the base class of all expressions.  It has two direct
subclasses ``Terminal`` and ``Operator``.

Subclasses of ``Terminal`` represent atomic quantities which
terminate the expression tree, e.g. they have no subexpressions.
Subclasses of ``Operator`` represent operations on one or more
other expressions, which may usually be ``Expr`` subclasses of
arbitrary type. Different ``Operator``\ s may have restrictions
on some properties of their arguments.

All the types mentioned here are conceptually immutable, i.e. they
should never be modified over the course of their entire lifetime. When a
modified expression, measure, integral, or form is needed, a new instance
must be created, possibly sharing some data with the old one. Since the
shared data is also immutable, sharing can cause no problems.

General properties of expressions
=================================

Any UFL expression has certain properties, defined by functions that
every ``Expr`` subclass must implement. In the following, ``u``
represents an arbitrary UFL expression, i.e. an instance of an
arbitrary ``Expr`` subclass.

``operands``
------------

``u.operands()`` returns a tuple with all the operands of u, which should
all be ``Expr`` instances.

``reconstruct``
---------------

``u.reconstruct(operands)`` returns a new ``Expr`` instance
representing the same operation as ``u`` but with other
operands. Terminal objects may simply return ``self`` since all
``Expr`` instance are immutable.  An important invariant is that
``u.reconstruct(u.operands()) == u``.

``cell``
--------

``u.cell()`` returns the first ``Cell`` instance found in ``u``. It
is currently assumed in UFL that no two different cells are used in
a single form. Not all expression define a cell, in which case this
returns ``None`` and ``u`` is spatially constant.  Note that this
property is used in some algorithms.


``shape``
---------

``u.shape()`` returns a tuple of integers, which is the tensor shape
of ``u``.


``free_indices``
-----------------

``u.free_indices()`` returns a tuple of ``Index`` objects, which
are the unassigned, free indices of ``u``.


``index_dimensions``
---------------------

``u.index_dimensions()`` returns a ``dict`` mapping from each
``Index`` instance in ``u.free_indices()`` to the integer dimension
of the value space each index can range over.


``str(u)``
----------

``str(u)`` returns a human-readable string representation of ``u``.


``repr(u)``
-----------

``repr(u)`` returns a Python string representation of ``u``, such
that ``eval(repr(u)) == u`` holds in Python.


``hash(u)``
-----------

``hash(u)`` returns a hash code for ``u``, which is used extensively
(indirectly) in algorithms whenever ``u`` is placed in a Python
``dict`` or ``set``.


``u == v``
----------

``u == v`` returns true if and only if ``u`` and ``v`` represents
the same expression in the exact same way.  This is used extensively
(indirectly) in algorithms whenever ``u`` is placed in a Python
``dict`` or ``set``.


About other relational operators
--------------------------------

In general, UFL expressions are not possible to fully evaluate since the
cell and the values of form arguments are not available. Implementing
relational operators for immediate evaluation is therefore impossible.

Overloading relational operators as a part of the form language is not
possible either, since it interferes with the correct use of container
types in Python like ``dict`` or ``set``.


Elements
========

All finite element classes have a common base class
``FiniteElementBase``. The class hierarchy looks like this:

..
TODO: Class figure.
..
TODO: Describe all FiniteElementBase subclasses here.


Terminals
=========

All ``Terminal`` subclasses have some non-``Expr`` data attached
to them. ``ScalarValue`` has a Python scalar, ``Coefficient``
has a ``FiniteElement``, etc.

Therefore, a unified implementation of ``reconstruct`` is
not possible, but since all ``Expr`` instances are immutable,
``reconstruct`` for terminals can simply return self. This feature
and the immutability property is used extensively in algorithms.

Operators
=========

All instances of ``Operator`` subclasses are fully specified
by their type plus the tuple of ``Expr`` instances that are
the operands. Their constructors should take these operands as the
positional arguments, and only that. This way, a unified implementation
of ``reconstruct`` is possible, by simply calling the constructor
with new operands. This feature is used extensively in algorithms.

Extending UFL
=============

Adding new types to the UFL class hierarchy must be done with care. If
you can get away with implementing a new operator as a combination of
existing ones, that is the easiest route. The reason is that only some
of the properties of an operator is represented by the ``Expr``
subclass. Other properties are part of the various algorithms in UFL.
One example is derivatives, which are defined in the differentiation
algorithm, and how to render a type to the dot formats. These properties
could be merged into the class hierarchy, but other properties like how
to map a UFL type to some ``ffc`` or ``dolfin`` type cannot be part of
UFL. So before adding a new class, consider that doing so may require
changes in multiple algorithms and even other projects.
