[ufl] 01/03: New upstream version 2017.1.0

Johannes Ring johannr-guest at moszumanska.debian.org
Wed May 10 10:42:17 UTC 2017


This is an automated email from the git hooks/post-receive script.

johannr-guest pushed a commit to branch experimental
in repository ufl.

commit 4844d838c3315cbd0cfc7f9111a0688740bc44c9
Author: Johannes Ring <johannr at simula.no>
Date:   Wed May 10 12:26:35 2017 +0200

    New upstream version 2017.1.0
---
 ChangeLog                                 | 275 ----------------------
 ChangeLog.rst                             | 379 ++++++++++++++++++++++++++++++
 MANIFEST.in                               |   8 +
 README.rst                                |   2 +-
 doc/sphinx/source/conf.py                 |  18 +-
 doc/sphinx/source/releases.rst            |   2 +
 doc/sphinx/source/releases/next.rst       |  20 --
 doc/sphinx/source/releases/v2016.2.0.rst  |  67 ++++++
 doc/sphinx/source/releases/v2017.1.0.rst  |  19 ++
 setup.cfg                                 |   2 +-
 setup.py                                  |   7 +-
 test/test_sobolevspace.py                 | 159 ++++++++++++-
 test/test_tensoralgebra.py                |  12 +-
 ufl/__init__.py                           |   9 +-
 ufl/algorithms/compute_form_data.py       |   5 +-
 ufl/algorithms/elementtransformations.py  |  36 +--
 ufl/constantvalue.py                      |   6 +-
 ufl/exproperators.py                      |  33 +++
 ufl/finiteelement/__init__.py             |   2 +
 ufl/finiteelement/brokenelement.py        |   3 +
 ufl/finiteelement/elementlist.py          |   1 +
 ufl/finiteelement/enrichedelement.py      |  74 +++++-
 ufl/finiteelement/facetelement.py         |  40 +---
 ufl/finiteelement/finiteelement.py        | 107 ++++++---
 ufl/finiteelement/finiteelementbase.py    |   1 -
 ufl/finiteelement/hdivcurl.py             |  15 ++
 ufl/finiteelement/interiorelement.py      |  40 +---
 ufl/finiteelement/mixedelement.py         |  12 +-
 ufl/finiteelement/restrictedelement.py    |   4 +
 ufl/finiteelement/tensorproductelement.py |  20 ++
 ufl/functionspace.py                      |  13 +
 ufl/log.py                                |   4 +-
 ufl/operators.py                          |   2 +
 ufl/sobolevspace.py                       | 118 ++++++++--
 ufl/utils/sorting.py                      |   2 +-
 35 files changed, 1024 insertions(+), 493 deletions(-)

diff --git a/ChangeLog b/ChangeLog
deleted file mode 100644
index 83209cb..0000000
--- a/ChangeLog
+++ /dev/null
@@ -1,275 +0,0 @@
-2016.2.0 [2016-11-30]
-  - Add call operator syntax to Form to replace arguments and
-    coefficients. This makes it easier to e.g. express the norm
-    defined by a bilinear form as a functional. Example usage:
-      # Equivalent to replace(a, {u: f, v: f})
-      M = a(f, f)
-      # Equivalent to replace(a, {f:1})
-      c = a(coefficients={f:1})
- - Add call operator syntax to Form to replace arguments and coefficients:
-     a(f, g) == replace(a, {u: f, v: g})
-     a(coefficients={f:1}) == replace(a, {f:1})
- - Add @ operator to Form: form @ f == action(form, f) (python 3.5+ only)
- - Reduce noise in Mesh str such that print(form) gets more short and readable
- - Fix repeated split(function) for arbitrary nested elements
- - EnrichedElement: Remove +/* warning
-    In the distant past, A + B => MixedElement([A, B]).  The change that
-    A + B => EnrichedElement([A, B]) was made in d622c74 (22 March 2010).
-    A warning was introduced in fcbc5ff (26 March 2010) that the meaning of
-    "+" had changed, and that users wanting a MixedElement should use "*"
-    instead.  People have, presumably, been seeing this warning for 6 1/2
-    years by now, so it's probably safe to remove.
- - Rework TensorProductElement implementation, replaces OuterProductElement
- - Rework TensorProductCell implementation, replaces OuterProductCell
- - Remove OuterProductVectorElement and OuterProductTensorElement
- - Add FacetElement and InteriorElement
- - Add Hellan-Herrmann-Johnson element
- - Add support for double covariant and contravariant mappings in mixed elements
- - Support discontinuous Taylor elements on all simplices
- - Some more performance improvements
- - Minor bugfixes
- - Improve Python 3 support
- - More permissive in integer types accepted some places
- - Make ufl pass almost all flake8 tests
- - Add bitbucket pipelines testing
- - Improve documentation
-2016.1.0 [2016-06-23]
- - Add operator A^(i,j) := as_tensor(A, (i,j))
- - Updates to old manual for publishing on fenics-ufl.readthedocs.org
- - Bugfix for ufl files with utf-8 encoding
- - Bugfix in conditional derivatives to avoid inf/nan values in generated
-   code. This bugfix may break ffc if uflacs is not used, to get around
-   that the old workaround in ufl can be enabled by setting
-   ufl.algorithms.apply_derivatives.CONDITIONAL_WORKAROUND = True
-   at the top of your program.
- - Allow sum([expressions]) where expressions are nonscalar by defining expr+0==expr
- - Allow form=0; form -= other;
- - Deprecate .cell(), .domain(), .element() in favour of .ufl_cell(),
-	.ufl_domain(), .ufl_element(), in multiple classes, to allow
-	closer integration with dolfin.
- - Remove deprecated properties cell.{d,x,n,volume,circumradius,facet_area}.
- - Remove ancient form2ufl script
- - Add new class Mesh to replace Domain
- - Add new class FunctionSpace(mesh, element)
- - Make FiniteElement classes take Cell, not Domain.
- - Large reworking of symbolic geometry pipeline
- - Implement symbolic Piola mappings
-1.6.0 [2015-07-28]
- - Change approach to attaching __hash__ implementation to accomodate python 3
- - Implement new non-recursive traversal based hash computation
- - Allow derivative(M, ListTensor(<scalars>), ...) just like list/tuple works
- - Add traits is_in_reference_frame, is_restriction, is_evaluation, is_differential
- - Add missing linear operators to ArgumentDependencyExtractor
- - Add _ufl_is_literal_ type trait
- - Add _ufl_is_terminal_modifier_ type trait and Expr._ufl_terminal_modifiers_ list
- - Add new types ReferenceDiv and ReferenceCurl
- - Outer product element support in degree estimation
- - Add TraceElement, InteriorElement, FacetElement, BrokenElement
- - Add OuterProductCell to valid Real elements
- - Add _cache member to form for use by external frameworks
- - Add Sobolev space HEin
- - Add measures dI,dO,dC for interface, overlap, cutcell
- - Remove Measure constants
- - Remove cell2D and cell3D
- - Implement reference_value in apply_restrictions
- - Rename point integral to vertex integral and kept *dP syntax
- - Replace lambda functions in ufl_type with named functions for nicer
-   stack traces
- - Minor bugfixes, removal of unused code and cleanups
-1.5.0 [2015-01-12]
- - Require Python 2.7
- - Python 3 support
- - Change to py.test
- - Rewrite parts of expression representation core, providing
-   significant optimizations in speed and memory use, as well
-   as a more elaborate type metadata system for internal use
- - Use expr.ufl_shape instead of ufl.shape()
- - Use expr.ufl_indices instead of ufl.indices(),
-   returns tuple of free index ids, not Index objects
- - Use expr.ufl_index_dimensions instead of ufl.index_dimensions(),
-   returns tuple of dimensions ordered corresponding to expr.ufl_indices, not a dict
- - Rewrite core algorithms for expression traversal
- - Add new core algorithms map_expr_dag(), map_integrand_dag(),
-   similar to python map() but applying a callable MultiFunction
-   recursively to each Expr node, without Python recursion
- - Highly recommend rewriting algorithms based on Transformer using
-   map_expr_dag and MultiFunction, avoiding Python recursion overhead
- - Rewrite core algorithms apply_derivatives, apply_restrictions
- - Form signature is now computed without applying derivatives first,
-   introducing smaller overhead on jit cache hits
- - Use form.signature() to compute form signature
- - Use form.arguments() instead of extract_arguments(form)
- - Use form.coefficients() instead of extract_coefficients(form)
- - Small improvement to str and latex output of expressions
- - Allow diff(expr, coefficient) without wrapping coefficient in variable
- - Add keywords to measures: dx(..., degree=3, rule="canonical")
- - Introduce notation from the Periodic Table of the Finite Elements
- - Introduce notation for FEEC families of elements: P-, P, Q-, S
- - Experimental support for high-order geometric domains
- - Algorithms for symbolic rewriting of geometric quantities (used by uflacs)
- - Remove the *Constant* classes, using Coefficient with a Real element instead
- - Add types for MinValue and MaxValue
- - Disable automatic rewriting a+a->2*a, a*a->a**2, a/a->1, these are
-   costly and the compiler should handle them instead
- - Fix signature stability w.r.t. metadata dicts
- - Minor bugfixes, removal of unused code and cleanups
-1.4.0 [2014-06-02]
- - New integral type custom_integral (*dc)
- - Add analysis of which coefficients each integral actually uses to optimize assembly
- - Improved svg rendering of cells and sobolevspaces in ipython notebook
- - Add sobolev spaces, use notation "element in HCurl" (HCurl, HDiv, H1, H2, L2)
- - Improved error checking of facet geometry in non-facet integrals
- - Improved restriction handling, restricting continuous coefficients and constants is now optional
- - Introduce notation from the Periodic Table of the Finite Elements (draft)
- - Remove alias "Q" for quadrature element, use "Quadrature"
- - New derivative type ReferenceGrad
- - New discontinuous RT element
- - New geometry types Jacobian, JacobianInverse, JacobianDeterminant
- - New geometry types FacetJacobian, FacetJacobianInverse, FacetJacobianDeterminant
- - New geometry types CellFacetJacobian, CellFacetJacobianInverse, CellFacetJacobianDeterminant
- - New geometry types FacetOrigin, CellOrigin
- - New geometry types CellCoordinate, FacetCoordinate
- - New geometry types CellNormal, CellOrientation, QuadratureWeight
- - Argument (and TestFunction, TrialFunction) now use absolute numbering f.number() instead of relative f.count()
- - New syntax: integrand*dx(domain)
- - New syntax: integrand*dx(1, domain=domain)
- - New syntax: integrand*dx(1, subdomain_data=domain_data)
- - Using domain instead of cell in many places.
- - Deprecated notation 'cell.n', 'cell.x' etc.
- - Recommended new notation: FacetNormal(domain)
- - Experimental: Argument (and TestFunction, TrialFunction) now can have a specified part index for representing block systems
- - Experimental: Domains can now be created with a Coefficient providing coordinates: Domain(Coefficient(VectorElement("CG", domain, 2)))
- - Experimental: New concept Domain: domain = Domain(triangle, geometric_dimension=3, label="MyDomain")
- - Various general optimizations
- - Various minor bugfixes
- - Various docstring improvements
-1.3.0 [2014-01-07]
- - Add cell_avg and facet_avg operators, can be applied to a Coefficient or Argument or restrictions thereof
- - Fix bug in cofactor: now it is transposed the correct way.
- - Add cell.min_facet_edge_length
- - Add cell.max_facet_edge_length
- - Simplify 0^f -> 0 if f is a non-negative scalar value
- - Add atan2 function
- - Allow form+0 -> form
-1.2.0 [2013-03-24]
- - NB! Using shapes such as (1,) and (1,1) instead of () for 1D tensor quantities I, x, grad(f)
- - Add cell.facet_diameter
- - Add new concept Domain
- - Add new concept Region, which is the union of numbered subdomains
- - Add integration over regions (which may be overlapping by sharing subdomains)
- - Add integration over everywhere
- - Add functions cosh, sinh, tanh, Max, Min
- - Generalize jump(v,n) for rank(v) > 2
- - Fix some minor bugs
-1.1.0 [2013-01-07]
- - Add support for pickling of expressions (thanks to Graham Markall)
- - Add shorthand notation A**2 == inner(A, A), special cased for power 2.
- - Add support for measure sum notation f*(dx(0) + dx(3)) == f*dx(0) + f*dx(3)
- - Supporting code for bugfix in PyDOLFIN when comparing test/trial functions
- - Remove support for tuple form notation as this was ambiguous
- - Bugfix in quadrature degree estimation, never returning <0 now
- - Remove use of cmp to accomodate removal from python 3
-1.1-alpha-prerelease [2012-11-18]
-(Not released, snapshot archived with submission of UFL journal paper)
- - Support adding 0 to forms, allowing sum([a])
- - Major memory savings and optimizations.
- - Some bugfixes.
- - Add perp operator.
- - Support nested tuple syntax like MixedElement((U,V),W)
- - Allow outer(a, b, c, ...) by recursive application from left.
- - Add simplification f/f -> 1
- - Add operators <,>,<=,>= in place of lt,gt,le,ge
-1.0.0 [2011-12-07]
- - No changes since rc1.
-1.0-rc1 [2011-11-22]
- - Added tests covering snippets from UFL chapter in FEniCS book
- - Added more unit tests
- - Added operators diag and diag_vector
- - Added geometric quantities cell.surface_area and cell.facet_area
- - Fixed rtruediv bug
- - Fixed bug with derivatives of elements of type Real with unspecified cell
-1.0-beta3 [2011-10-26]
- - Added nabla_grad and nabla_div operators
- - Added error function erf(x)
- - Added bessel functions of first and second kind, normal and modified,
-   bessel_J(nu, x), bessel_Y(nu, x), bessel_I(nu, x), bessel_K(nu, x)
- - Extended derivative() to allow indexed coefficient(s) as differentiation variable
- - Made *Constant use the 'Real' space instead of 'DG0'
- - Bugfix in adjoint where test and trial functions were in different spaces
- - Bugfix in replace where the argument to a grad was replaced with 0
- - Bugfix in reconstruction of tensor elements
- - Some other minor bugfixes
-1.0-beta2 [2011-08-11]
- - Support c*form where c depends on a coefficient in a Real space
-1.0-beta [2011-07-08]
- - Add script ufl-version
- - Added syntax for associating an arbitrary domain data object with a measure:
-	dss = ds[boundaries]; M = f*dss(1) + g*dss(2)
- - Added new operators elem_mult, elem_div, elem_pow and elem_op for
-   elementwise application of scalar operators to tensors of equal shape
- - Added condition operators And(lhs,rhs) and Or(lhs,rhs) and Not(cond)
- - Fixed support for symmetries in subelements of a mixed element
- - Add support for specifying derivatives of coefficients to derivative()
-0.9.1 [2011-05-16]
- - Remove set_foo functions in finite element classes
- - Change license from GPL v3 or later to LGPL v3 or later
- - Change behavior of preprocess(), form.compute_form_data(), form_data.preprocessed_form
- - Allowing grad, div, inner, dot, det, inverse on scalars
- - Simplify Identity(1) -> IntValue(1) automatically
- - Added Levi-Cevita symbol: e = PermutationSymbol(3); e[i,j,k]
- - Fix bug with future division behaviour (ufl does not support floor division)
- - Add subdomain member variables to form class
- - Allow action on forms of arbitrary rank
-0.9.0 [2011-02-23]
- - Allow jump(Sigma, n) for matrix-valued expression Sigma
- - Bug fix in scalar curl operator
- - Bug fix in deviatoric operator
-0.5.4 [2010-09-01]
- - Bug fixes in PartExtracter
- - Do not import x for coordinate
- - Add Circumradius to Cell (Cell.circumradius)
- - Add CellVolume to Cell (Cell.volume)
-0.5.3 [2010-07-01]
- - Rename ElementRestriction --> RestrictedElement
- - Experimental import of x from tetrahedron
- - Make lhs/rhs work for resrictions
- - Redefine operator + for FiniteElements and replace + by *
- - Rename ElementUnion -> EnrichedElement
- - Add support for tan() and inverse trigonometric functions
-0.5.2 [2010-02-15]
- - Attach form data to preprocessed form, accessible by form.form_data()
-0.5.1 [2010-02-03]
- - Fix bug in propagate_restriction
-0.5.0 [2010-02-01]
- - Several interface changes in FormData class
- - Introduce call preprocess(form) to be called at beginning of compilation
- - Rename BasisFunction --> Argument
- - Rename Function --> Coefficient
-0.4.1 [2009-12-04]
- - Redefine grad().T --> grad()
- - New meaning of estimate_max_polynomial_degree
- - New function estimate_total_polynomial_degree
- - Allow degree = None and cell = None for elements
-0.4.0 [2009-09-23]
- - Extensions for ElementRestriction (restrict FiniteElement to Cell)
- - Bug fix for lhs/rhs with list tensor types
- - Add new log function set_prefix
- - Add new log function log(level, message)
- - Added macro cell integral *dE
- - Added mechanism to add additional integral types
- - Added LiftingOperator and LiftingFunction
- - Added ElementRestriction
-0.3.0 [2009-05-28]
- - Some critical bugfixes, in particular in differentiation.
- - Added form operators "system" and "sensitivity_rhs".
- - diff can take form as argument, applies to all integrands.
- - Rudimentary precedence handling for better
-   use of parentheses in str(expression).
- - Added script ufl2py, mainly for debugging purposes.
- - Crude implementation of estimate_max_polynomial_degree
-   for quadrature degree estimation.
- - Improved manual.
-0.2.0 [2009-04-07]
- - Initial release of UFL.
-0.1.0 [...]
- - Unreleased development versions of UFL.
diff --git a/ChangeLog.rst b/ChangeLog.rst
new file mode 100644
index 0000000..caa2be5
--- /dev/null
+++ b/ChangeLog.rst
@@ -0,0 +1,379 @@
+Changelog
+=========
+
+2017.1.0 (2017-05-09)
+---------------------
+
+- Add the ``DirectionalSobolevSpace`` subclass of ``SobolevSpace``. This
+  allows one to use spaces where elements have varying continuity in
+  different spatial directions.
+- Add ``sobolev_space`` methods for ``HDiv`` and ``HCurl`` finite
+  elements.
+- Add ``sobolev_space`` methods for ``TensorProductElement`` and
+  ``EnrichedElement``.  The smallest shared Sobolev space will be
+  returned for enriched elements. For the tensor product elements, a
+  ``DirectionalSobolevSpace`` is returned depending on the order of the
+  spaces associated with the component elements.
+
+2016.2.0 (2016-11-30)
+---------------------
+
+- Add call operator syntax to ``Form`` to replace arguments and
+  coefficients. This makes it easier to e.g. express the norm
+  defined by a bilinear form as a functional. Example usage::
+
+    # Equivalent to replace(a, {u: f, v: f})
+    M = a(f, f)
+    # Equivalent to replace(a, {f:1})
+    c = a(coefficients={f:1})
+- Add call operator syntax to ``Form`` to replace arguments and
+  coefficients::
+
+    a(f, g) == replace(a, {u: f, v: g})
+    a(coefficients={f:1}) == replace(a, {f:1})
+- Add ``@`` operator to ``Form``: ``form @ f == action(form, f)``
+  (python 3.5+ only)
+- Reduce noise in Mesh str such that ``print(form)`` gets more short and
+  readable
+- Fix repeated ``split(function)`` for arbitrary nested elements
+- EnrichedElement: Remove ``+/*`` warning
+
+  In the distant past, ``A + B => MixedElement([A, B])``.  The change
+  that ``A + B => EnrichedElement([A, B])`` was made in ``d622c74`` (22
+  March 2010).  A warning was introduced in ``fcbc5ff`` (26 March 2010)
+  that the meaning of ``+`` had changed, and that users wanting a
+  ``MixedElement`` should use ``*`` instead.  People have, presumably,
+  been seeing this warning for 6 1/2 years by now, so it's probably safe
+  to remove.
+- Rework ``TensorProductElement`` implementation, replaces
+  ``OuterProductElement``
+- Rework ``TensorProductCell`` implementation, replaces
+  ``OuterProductCell``
+- Remove ``OuterProductVectorElement`` and ``OuterProductTensorElement``
+- Add ``FacetElement`` and ``InteriorElement``
+- Add ``Hellan-Herrmann-Johnson`` element
+- Add support for double covariant and contravariant mappings in mixed
+  elements
+- Support discontinuous Taylor elements on all simplices
+- Some more performance improvements
+- Minor bugfixes
+- Improve Python 3 support
+- More permissive in integer types accepted some places
+- Make ufl pass almost all flake8 tests
+- Add bitbucket pipelines testing
+- Improve documentation
+
+2016.1.0 (2016-06-23)
+---------------------
+
+- Add operator A^(i,j) := as_tensor(A, (i,j))
+- Updates to old manual for publishing on fenics-ufl.readthedocs.org
+- Bugfix for ufl files with utf-8 encoding
+- Bugfix in conditional derivatives to avoid inf/nan values in generated
+  code. This bugfix may break ffc if uflacs is not used, to get around
+  that the old workaround in ufl can be enabled by setting
+  ufl.algorithms.apply_derivatives.CONDITIONAL_WORKAROUND = True
+  at the top of your program.
+- Allow sum([expressions]) where expressions are nonscalar by defining expr+0==expr
+- Allow form=0; form -= other;
+- Deprecate .cell(), .domain(), .element() in favour of .ufl_cell(),
+	.ufl_domain(), .ufl_element(), in multiple classes, to allow
+	closer integration with dolfin.
+- Remove deprecated properties cell.{d,x,n,volume,circumradius,facet_area}.
+- Remove ancient form2ufl script
+- Add new class Mesh to replace Domain
+- Add new class FunctionSpace(mesh, element)
+- Make FiniteElement classes take Cell, not Domain.
+- Large reworking of symbolic geometry pipeline
+- Implement symbolic Piola mappings
+
+1.6.0 (2015-07-28)
+------------------
+
+- Change approach to attaching __hash__ implementation to accomodate python 3
+- Implement new non-recursive traversal based hash computation
+- Allow derivative(M, ListTensor(<scalars>), ...) just like list/tuple works
+- Add traits is_in_reference_frame, is_restriction, is_evaluation, is_differential
+- Add missing linear operators to ArgumentDependencyExtractor
+- Add _ufl_is_literal_ type trait
+- Add _ufl_is_terminal_modifier_ type trait and Expr._ufl_terminal_modifiers_ list
+- Add new types ReferenceDiv and ReferenceCurl
+- Outer product element support in degree estimation
+- Add TraceElement, InteriorElement, FacetElement, BrokenElement
+- Add OuterProductCell to valid Real elements
+- Add _cache member to form for use by external frameworks
+- Add Sobolev space HEin
+- Add measures dI,dO,dC for interface, overlap, cutcell
+- Remove Measure constants
+- Remove cell2D and cell3D
+- Implement reference_value in apply_restrictions
+- Rename point integral to vertex integral and kept ``*dP`` syntax
+- Replace lambda functions in ufl_type with named functions for nicer
+  stack traces
+- Minor bugfixes, removal of unused code and cleanups
+
+1.5.0 (2015-01-12)
+------------------
+
+- Require Python 2.7
+- Python 3 support
+- Change to py.test
+- Rewrite parts of expression representation core, providing
+  significant optimizations in speed and memory use, as well
+  as a more elaborate type metadata system for internal use
+- Use expr.ufl_shape instead of ufl.shape()
+- Use expr.ufl_indices instead of ufl.indices(),
+  returns tuple of free index ids, not Index objects
+- Use expr.ufl_index_dimensions instead of ufl.index_dimensions(),
+  returns tuple of dimensions ordered corresponding to expr.ufl_indices, not a dict
+- Rewrite core algorithms for expression traversal
+- Add new core algorithms map_expr_dag(), map_integrand_dag(),
+  similar to python map() but applying a callable MultiFunction
+  recursively to each Expr node, without Python recursion
+- Highly recommend rewriting algorithms based on Transformer using
+  map_expr_dag and MultiFunction, avoiding Python recursion overhead
+- Rewrite core algorithms apply_derivatives, apply_restrictions
+- Form signature is now computed without applying derivatives first,
+  introducing smaller overhead on jit cache hits
+- Use form.signature() to compute form signature
+- Use form.arguments() instead of extract_arguments(form)
+- Use form.coefficients() instead of extract_coefficients(form)
+- Small improvement to str and latex output of expressions
+- Allow diff(expr, coefficient) without wrapping coefficient in variable
+- Add keywords to measures: dx(..., degree=3, rule="canonical")
+- Introduce notation from the Periodic Table of the Finite Elements
+- Introduce notation for FEEC families of elements: P-, P, Q-, S
+- Experimental support for high-order geometric domains
+- Algorithms for symbolic rewriting of geometric quantities (used by uflacs)
+- Remove the *Constant* classes, using Coefficient with a Real element instead
+- Add types for MinValue and MaxValue
+- Disable automatic rewriting a+a->2*a, a*a->a**2, a/a->1, these are
+  costly and the compiler should handle them instead
+- Fix signature stability w.r.t. metadata dicts
+- Minor bugfixes, removal of unused code and cleanups
+
+1.4.0 (2014-06-02)
+------------------
+
+- New integral type custom_integral (``*dc``)
+- Add analysis of which coefficients each integral actually uses to optimize assembly
+- Improved svg rendering of cells and sobolevspaces in ipython notebook
+- Add sobolev spaces, use notation "element in HCurl" (HCurl, HDiv, H1, H2, L2)
+- Improved error checking of facet geometry in non-facet integrals
+- Improved restriction handling, restricting continuous coefficients and constants is now optional
+- Introduce notation from the Periodic Table of the Finite Elements (draft)
+- Remove alias "Q" for quadrature element, use "Quadrature"
+- New derivative type ReferenceGrad
+- New discontinuous RT element
+- New geometry types Jacobian, JacobianInverse, JacobianDeterminant
+- New geometry types FacetJacobian, FacetJacobianInverse, FacetJacobianDeterminant
+- New geometry types CellFacetJacobian, CellFacetJacobianInverse, CellFacetJacobianDeterminant
+- New geometry types FacetOrigin, CellOrigin
+- New geometry types CellCoordinate, FacetCoordinate
+- New geometry types CellNormal, CellOrientation, QuadratureWeight
+- Argument (and TestFunction, TrialFunction) now use absolute numbering f.number() instead of relative f.count()
+- New syntax: integrand*dx(domain)
+- New syntax: integrand*dx(1, domain=domain)
+- New syntax: integrand*dx(1, subdomain_data=domain_data)
+- Using domain instead of cell in many places.
+- Deprecated notation 'cell.n', 'cell.x' etc.
+- Recommended new notation: FacetNormal(domain)
+- Experimental: Argument (and TestFunction, TrialFunction) now can have a specified part index for representing block systems
+- Experimental: Domains can now be created with a Coefficient providing coordinates: Domain(Coefficient(VectorElement("CG", domain, 2)))
+- Experimental: New concept Domain: domain = Domain(triangle, geometric_dimension=3, label="MyDomain")
+- Various general optimizations
+- Various minor bugfixes
+- Various docstring improvements
+
+1.3.0 (2014-01-07)
+------------------
+
+- Add cell_avg and facet_avg operators, can be applied to a Coefficient or Argument or restrictions thereof
+- Fix bug in cofactor: now it is transposed the correct way.
+- Add cell.min_facet_edge_length
+- Add cell.max_facet_edge_length
+- Simplify 0^f -> 0 if f is a non-negative scalar value
+- Add atan2 function
+- Allow form+0 -> form
+
+1.2.0 (2013-03-24)
+------------------
+
+- NB! Using shapes such as (1,) and (1,1) instead of () for 1D tensor quantities I, x, grad(f)
+- Add cell.facet_diameter
+- Add new concept Domain
+- Add new concept Region, which is the union of numbered subdomains
+- Add integration over regions (which may be overlapping by sharing subdomains)
+- Add integration over everywhere
+- Add functions cosh, sinh, tanh, Max, Min
+- Generalize jump(v,n) for rank(v) > 2
+- Fix some minor bugs
+
+1.1.0 (2013-01-07)
+------------------
+
+- Add support for pickling of expressions (thanks to Graham Markall)
+- Add shorthand notation A**2 == inner(A, A), special cased for power 2.
+- Add support for measure sum notation f*(dx(0) + dx(3)) == f*dx(0) + f*dx(3)
+- Supporting code for bugfix in PyDOLFIN when comparing test/trial functions
+- Remove support for tuple form notation as this was ambiguous
+- Bugfix in quadrature degree estimation, never returning <0 now
+- Remove use of cmp to accomodate removal from python 3
+
+1.1-alpha-prerelease (2012-11-18)
+---------------------------------
+
+(Not released, snapshot archived with submission of UFL journal paper)
+- Support adding 0 to forms, allowing sum([a])
+- Major memory savings and optimizations.
+- Some bugfixes.
+- Add perp operator.
+- Support nested tuple syntax like MixedElement((U,V),W)
+- Allow outer(a, b, c, ...) by recursive application from left.
+- Add simplification f/f -> 1
+- Add operators <,>,<=,>= in place of lt,gt,le,ge
+
+1.0.0 (2011-12-07)
+------------------
+
+- No changes since rc1.
+
+1.0-rc1 (2011-11-22)
+--------------------
+
+- Added tests covering snippets from UFL chapter in FEniCS book
+- Added more unit tests
+- Added operators diag and diag_vector
+- Added geometric quantities cell.surface_area and cell.facet_area
+- Fixed rtruediv bug
+- Fixed bug with derivatives of elements of type Real with unspecified cell
+
+1.0-beta3 (2011-10-26)
+----------------------
+
+- Added nabla_grad and nabla_div operators
+- Added error function erf(x)
+- Added bessel functions of first and second kind, normal and modified,
+  bessel_J(nu, x), bessel_Y(nu, x), bessel_I(nu, x), bessel_K(nu, x)
+- Extended derivative() to allow indexed coefficient(s) as differentiation variable
+- Made ``*Constant`` use the ``Real`` space instead of ``DG0``
+- Bugfix in adjoint where test and trial functions were in different spaces
+- Bugfix in replace where the argument to a grad was replaced with 0
+- Bugfix in reconstruction of tensor elements
+- Some other minor bugfixes
+
+1.0-beta2 (2011-08-11)
+----------------------
+
+- Support c*form where c depends on a coefficient in a Real space
+
+1.0-beta (2011-07-08)
+---------------------
+
+- Add script ufl-version
+- Added syntax for associating an arbitrary domain data object with a measure:
+	dss = ds[boundaries]; M = f*dss(1) + g*dss(2)
+- Added new operators elem_mult, elem_div, elem_pow and elem_op for
+  elementwise application of scalar operators to tensors of equal shape
+- Added condition operators And(lhs,rhs) and Or(lhs,rhs) and Not(cond)
+- Fixed support for symmetries in subelements of a mixed element
+- Add support for specifying derivatives of coefficients to derivative()
+
+0.9.1 (2011-05-16)
+------------------
+
+- Remove set_foo functions in finite element classes
+- Change license from GPL v3 or later to LGPL v3 or later
+- Change behavior of preprocess(), form.compute_form_data(), form_data.preprocessed_form
+- Allowing grad, div, inner, dot, det, inverse on scalars
+- Simplify Identity(1) -> IntValue(1) automatically
+- Added Levi-Cevita symbol: e = PermutationSymbol(3); e[i,j,k]
+- Fix bug with future division behaviour (ufl does not support floor division)
+- Add subdomain member variables to form class
+- Allow action on forms of arbitrary rank
+
+0.9.0 (2011-02-23)
+------------------
+
+- Allow jump(Sigma, n) for matrix-valued expression Sigma
+- Bug fix in scalar curl operator
+- Bug fix in deviatoric operator
+
+0.5.4 (2010-09-01)
+------------------
+
+- Bug fixes in PartExtracter
+- Do not import x for coordinate
+- Add Circumradius to Cell (Cell.circumradius)
+- Add CellVolume to Cell (Cell.volume)
+
+0.5.3 (2010-07-01)
+------------------
+
+- Rename ElementRestriction --> RestrictedElement
+- Experimental import of x from tetrahedron
+- Make lhs/rhs work for resrictions
+- Redefine operator + for FiniteElements and replace + by *
+- Rename ElementUnion -> EnrichedElement
+- Add support for tan() and inverse trigonometric functions
+
+0.5.2 (2010-02-15)
+------------------
+
+- Attach form data to preprocessed form, accessible by form.form_data()
+
+0.5.1 (2010-02-03)
+------------------
+
+- Fix bug in propagate_restriction
+
+0.5.0 (2010-02-01)
+------------------
+
+- Several interface changes in FormData class
+- Introduce call preprocess(form) to be called at beginning of compilation
+- Rename BasisFunction --> Argument
+- Rename Function --> Coefficient
+
+0.4.1 (2009-12-04)
+------------------
+
+- Redefine grad().T --> grad()
+- New meaning of estimate_max_polynomial_degree
+- New function estimate_total_polynomial_degree
+- Allow degree = None and cell = None for elements
+
+0.4.0 (2009-09-23)
+------------------
+
+- Extensions for ElementRestriction (restrict FiniteElement to Cell)
+- Bug fix for lhs/rhs with list tensor types
+- Add new log function set_prefix
+- Add new log function log(level, message)
+- Added macro cell integral ``*dE``
+- Added mechanism to add additional integral types
+- Added LiftingOperator and LiftingFunction
+- Added ElementRestriction
+
+0.3.0 (2009-05-28)
+------------------
+
+- Some critical bugfixes, in particular in differentiation.
+- Added form operators "system" and "sensitivity_rhs".
+- diff can take form as argument, applies to all integrands.
+- Rudimentary precedence handling for better
+  use of parentheses in str(expression).
+- Added script ufl2py, mainly for debugging purposes.
+- Crude implementation of estimate_max_polynomial_degree
+  for quadrature degree estimation.
+- Improved manual.
+
+0.2.0 (2009-04-07)
+------------------
+
+- Initial release of UFL.
+
+0.1.0 (unreleased)
+------------------
+
+- Unreleased development versions of UFL.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..5069eb5
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,8 @@
+include AUTHORS
+include COPYING
+include COPYING.LESSER
+include ChangeLog
+recursive-include demo *
+recursive-include doc *
+recursive-include test *
+global-exclude __pycache__ *.pyc
diff --git a/README.rst b/README.rst
index 32db38c..61a6e53 100644
--- a/README.rst
+++ b/README.rst
@@ -42,7 +42,7 @@ Code Coverage
 =============
 
 Code coverage reports can be viewed at
-https://coveralls.io/repos/bitbucket/fenics-project/ufl.
+https://coveralls.io/bitbucket/fenics-project/ufl.
 
 .. image:: https://coveralls.io/repos/bitbucket/fenics-project/ufl/badge.svg?branch=master
    :target: https://coveralls.io/bitbucket/fenics-project/ufl?branch=master
diff --git a/doc/sphinx/source/conf.py b/doc/sphinx/source/conf.py
index d662b0e..d8a68e5 100644
--- a/doc/sphinx/source/conf.py
+++ b/doc/sphinx/source/conf.py
@@ -15,6 +15,8 @@
 import sys
 import os
 import shlex
+import pkg_resources
+import datetime
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
@@ -53,19 +55,11 @@ master_doc = 'index'
 
 # General information about the project.
 project = u'Unified Form Language (UFL)'
-copyright = u'2016, FEniCS Project'
+this_year = datetime.date.today().year
+copyright = u'%s, FEniCS Project' % this_year
 author = u'FEniCS Project'
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-import ufl
-ufl_version = ufl.__version__
-# The short X.Y version.
-version = ufl_version
-# The full version, including alpha/beta/rc tags.
-release = ufl_version
+version = pkg_resources.get_distribution("ufl").version
+release = version
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/doc/sphinx/source/releases.rst b/doc/sphinx/source/releases.rst
index 796ed29..ee8b507 100644
--- a/doc/sphinx/source/releases.rst
+++ b/doc/sphinx/source/releases.rst
@@ -9,5 +9,7 @@ Release notes
    :maxdepth: 2
 
    releases/next
+   releases/v2017.1.0
+   releases/v2016.2.0
    releases/v2016.1.0
    releases/v1.6.0
diff --git a/doc/sphinx/source/releases/next.rst b/doc/sphinx/source/releases/next.rst
index a8dff28..cdcd4a3 100644
--- a/doc/sphinx/source/releases/next.rst
+++ b/doc/sphinx/source/releases/next.rst
@@ -11,26 +11,6 @@ Summary of changes
           be published (and renamed) to list the most important
           changes in the new release.
 
-- Deprecate ``.cell()``, ``.domain()``, ``.element()`` in favour of
-  ``.ufl_cell()``, ``.ufl_domain()``, ``.ufl_element()``, in multiple
-  classes, to allow closer integration with DOLFIN
-- Remove deprecated properties
-  ``cell.{d,x,n,volume,circumradius,facet_area}``
-- Remove ancient ``form2ufl`` script
-- Large reworking of symbolic geometry pipeline
-- Implement symbolic Piola mappings
-- ``OuterProductCell`` and ``OuterProductElement`` are merged into
-  ``TensorProductCell`` and ``TensorProductElement`` respectively
-- Better degree estimation for quadrilaterals
-- Expansion rules for Q, DQ, RTCE, RTCF, NCE and NCF on tensor product
-  cells
-- Add discontinuous Taylor elements
-- Add support for the mapping ``double covariant Piola`` in ``uflacs``
-- Add support for the mapping ``double contravariant Piola`` in ``uflacs``
-- Support for tensor-valued subelements in ``uflacs`` fixed
-- Replacing ``Discontinuous Lagrange Trace`` with ``HDiv Trace`` and removing ``TraceElement``
-- Assigning ``Discontinuous Lagrange Trace`` and ``DGT`` as aliases for ``HDiv Trace``
-
 Detailed changes
 ================
 
diff --git a/doc/sphinx/source/releases/v2016.2.0.rst b/doc/sphinx/source/releases/v2016.2.0.rst
new file mode 100644
index 0000000..44c21cb
--- /dev/null
+++ b/doc/sphinx/source/releases/v2016.2.0.rst
@@ -0,0 +1,67 @@
+===========================
+Changes in version 2016.2.0
+===========================
+
+UFL 2016.2.0 was released on 2016-11-30.
+
+
+Summary of changes
+==================
+
+- Deprecate ``.cell()``, ``.domain()``, ``.element()`` in favour of
+  ``.ufl_cell()``, ``.ufl_domain()``, ``.ufl_element()``, in multiple
+  classes, to allow closer integration with DOLFIN
+- Remove deprecated properties
+  ``cell.{d,x,n,volume,circumradius,facet_area}``
+- Remove ancient ``form2ufl`` script
+- Large reworking of symbolic geometry pipeline
+- Implement symbolic Piola mappings
+- ``OuterProductCell`` and ``OuterProductElement`` are merged into
+  ``TensorProductCell`` and ``TensorProductElement`` respectively
+- Better degree estimation for quadrilaterals
+- Expansion rules for Q, DQ, RTCE, RTCF, NCE and NCF on tensor product
+  cells
+- Add discontinuous Taylor elements
+- Add support for the mapping ``double covariant Piola`` in ``uflacs``
+- Add support for the mapping ``double contravariant Piola`` in ``uflacs``
+- Support for tensor-valued subelements in ``uflacs`` fixed
+- Replacing ``Discontinuous Lagrange Trace`` with ``HDiv Trace`` and removing ``TraceElement``
+- Assigning ``Discontinuous Lagrange Trace`` and ``DGT`` as aliases for ``HDiv Trace``
+
+Detailed changes
+================
+
+- Add call operator syntax to Form to replace arguments and
+  coefficients. This makes it easier to e.g. express the norm
+  defined by a bilinear form as a functional. Example usage::
+     # Equivalent to replace(a, {u: f, v: f})
+     M = a(f, f)
+     # Equivalent to replace(a, {f:1})
+     c = a(coefficients={f:1})
+- Add call operator syntax to Form to replace arguments and coefficients::
+    a(f, g) == replace(a, {u: f, v: g})
+    a(coefficients={f:1}) == replace(a, {f:1})
+- Add @ operator to Form: form @ f == action(form, f) (python 3.5+ only)
+- Reduce noise in Mesh str such that print(form) gets more short and readable
+- Fix repeated split(function) for arbitrary nested elements
+- EnrichedElement: Remove +/* warning
+   In the distant past, A + B => MixedElement([A, B]).  The change that
+   A + B => EnrichedElement([A, B]) was made in d622c74 (22 March 2010).
+   A warning was introduced in fcbc5ff (26 March 2010) that the meaning of
+   "+" had changed, and that users wanting a MixedElement should use "*"
+   instead.  People have, presumably, been seeing this warning for 6 1/2
+   years by now, so it's probably safe to remove.
+- Rework TensorProductElement implementation, replaces OuterProductElement
+- Rework TensorProductCell implementation, replaces OuterProductCell
+- Remove OuterProductVectorElement and OuterProductTensorElement
+- Add FacetElement and InteriorElement
+- Add Hellan-Herrmann-Johnson element
+- Add support for double covariant and contravariant mappings in mixed elements
+- Support discontinuous Taylor elements on all simplices
+- Some more performance improvements
+- Minor bugfixes
+- Improve Python 3 support
+- More permissive in integer types accepted some places
+- Make ufl pass almost all flake8 tests
+- Add bitbucket pipelines testing
+- Improve documentation
diff --git a/doc/sphinx/source/releases/v2017.1.0.rst b/doc/sphinx/source/releases/v2017.1.0.rst
new file mode 100644
index 0000000..fae6181
--- /dev/null
+++ b/doc/sphinx/source/releases/v2017.1.0.rst
@@ -0,0 +1,19 @@
+===========================
+Changes in version 2017.1.0
+===========================
+
+UFL 2017.1.0 was released on 2017-05-09.
+
+Summary of changes
+==================
+
+- Add the ``DirectionalSobolevSpace`` subclass of ``SobolevSpace``. This
+  allows one to use spaces where elements have varying continuity in
+  different spatial directions.
+- Add ``sobolev_space`` methods for ``HDiv`` and ``HCurl`` finite
+  elements.
+- Add ``sobolev_space`` methods for ``TensorProductElement`` and
+  ``EnrichedElement``.  The smallest shared Sobolev space will be
+  returned for enriched elements. For the tensor product elements, a
+  ``DirectionalSobolevSpace`` is returned depending on the order of the
+  spaces associated with the component elements.
diff --git a/setup.cfg b/setup.cfg
index dc791ed..f597cbd 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,3 @@
 [flake8]
-ignore = E501,E226,W503,E127,E123,E128,E265
+ignore = E305,E501,E226,W503,E127,E123,E128,E265
 exclude = .git,__pycache__,doc/sphinx/source/conf.py,build,dist,test
diff --git a/setup.py b/setup.py
index 684278c..dd79757 100755
--- a/setup.py
+++ b/setup.py
@@ -3,10 +3,8 @@ from __future__ import print_function
 
 from setuptools import setup
 from os.path import join, split
-import re
 import sys
 import platform
-import codecs
 
 module_name = "ufl"
 
@@ -14,10 +12,7 @@ if sys.version_info < (2, 7):
     print("Python 2.7 or higher required, please upgrade.")
     sys.exit(1)
 
-# __init__.py has UTF-8 characters. Works in Python 2 and 3.
-version = re.findall('__version__ = "(.*)"',
-                     codecs.open(join(module_name, '__init__.py'), 'r',
-                                 encoding='utf-8').read())[0]
+version = "2017.1.0"
 
 url = "https://bitbucket.org/fenics-project/%s/" % module_name
 tarball = None
diff --git a/test/test_sobolevspace.py b/test/test_sobolevspace.py
index 0b32494..65c665b 100755
--- a/test/test_sobolevspace.py
+++ b/test/test_sobolevspace.py
@@ -5,10 +5,25 @@ __authors__ = "David Ham"
 __date__ = "2014-03-04"
 
 import pytest
-from ufl import FiniteElement, triangle
-from ufl.sobolevspace import SobolevSpace
+from ufl import (EnrichedElement, TensorProductElement,
+                 FiniteElement, triangle, interval,
+                 quadrilateral, HDiv, HCurl)
+from ufl.sobolevspace import SobolevSpace, DirectionalSobolevSpace
 from ufl import H2, H1, HDiv, HCurl, L2
 
+
+# Construct directional Sobolev spaces, with varying smoothness in
+# spatial coordinates
+H0dx0dy = DirectionalSobolevSpace((0, 0))
+H1dx1dy = DirectionalSobolevSpace((1, 1))
+H2dx2dy = DirectionalSobolevSpace((2, 2))
+H1dx = DirectionalSobolevSpace((1, 0))
+H1dy = DirectionalSobolevSpace((0, 1))
+H000 = DirectionalSobolevSpace((0, 0, 0))
+H1dz = DirectionalSobolevSpace((0, 0, 1))
+H1dh = DirectionalSobolevSpace((1, 1, 0))
+H2dhH1dz = DirectionalSobolevSpace((2, 2, 1))
+
 # TODO: Add construction of all elements with periodic table notation here.
 
 
@@ -17,6 +32,25 @@ def test_inclusion():
     assert not H2 > H1   # Not included
     assert HDiv <= HDiv  # Reflexivity
     assert H2 < L2       # Transitivity
+    assert H1 > H2
+    assert L2 > H1
+
+
+def test_directional_space_relations():
+    assert H0dx0dy == L2
+    assert H1dx1dy == H1
+    assert H2dx2dy == H2
+    assert H1dx1dy <= HDiv
+    assert H1dx1dy <= HCurl
+    assert H2dx2dy <= H1dx1dy
+    assert H2dhH1dz < H1
+    assert H1dz > H2dhH1dz
+    assert H1dh < L2
+    assert H1dz < L2
+    assert L2 > H1dx
+    assert L2 > H1dy
+    assert not H1dh <= HDiv
+    assert not H1dh <= HCurl
 
 
 def test_repr():
@@ -34,13 +68,24 @@ def test_contains_l2():
         FiniteElement("DG", triangle, 1),
         FiniteElement("DG", triangle, 2),
         FiniteElement("CR", triangle, 1),
+        # Tensor product elements:
+        TensorProductElement(FiniteElement("DG", interval, 1),
+                             FiniteElement("DG", interval, 1)),
+        TensorProductElement(FiniteElement("DG", interval, 1),
+                             FiniteElement("CG", interval, 2)),
+        # Enriched element:
+        EnrichedElement(FiniteElement("DG", triangle, 1),
+                        FiniteElement("B", triangle, 3))
     ]
     for l2_element in l2_elements:
         assert l2_element in L2
+        assert l2_element in H0dx0dy
         assert l2_element not in H1
+        assert l2_element not in H1dx1dy
         assert l2_element not in HCurl
         assert l2_element not in HDiv
         assert l2_element not in H2
+        assert l2_element not in H2dx2dy
 
 
 def test_contains_h1():
@@ -52,13 +97,24 @@ def test_contains_h1():
         FiniteElement("AW", triangle),
         FiniteElement("HER", triangle),
         FiniteElement("MTW", triangle),
+        # Tensor product elements:
+        TensorProductElement(FiniteElement("CG", interval, 1),
+                             FiniteElement("CG", interval, 1)),
+        TensorProductElement(FiniteElement("CG", interval, 2),
+                             FiniteElement("CG", interval, 2)),
+        # Enriched elements:
+        EnrichedElement(FiniteElement("CG", triangle, 2),
+                        FiniteElement("B", triangle, 3))
     ]
     for h1_element in h1_elements:
         assert h1_element in H1
+        assert h1_element in H1dx1dy
         assert h1_element in HDiv
         assert h1_element in HCurl
         assert h1_element in L2
+        assert h1_element in H0dx0dy
         assert h1_element not in H2
+        assert h1_element not in H2dx2dy
 
 
 def test_contains_h2():
@@ -68,10 +124,13 @@ def test_contains_h2():
     ]
     for h2_element in h2_elements:
         assert h2_element in H2
+        assert h2_element in H2dx2dy
         assert h2_element in H1
+        assert h2_element in H1dx1dy
         assert h2_element in HDiv
         assert h2_element in HCurl
         assert h2_element in L2
+        assert h2_element in H0dx0dy
 
 
 def test_contains_hdiv():
@@ -79,23 +138,119 @@ def test_contains_hdiv():
         FiniteElement("RT", triangle, 1),
         FiniteElement("BDM", triangle, 1),
         FiniteElement("BDFM", triangle, 2),
+        # HDiv elements:
+        HDiv(TensorProductElement(FiniteElement("DG", triangle, 1),
+                                  FiniteElement("CG", interval, 2))),
+        HDiv(TensorProductElement(FiniteElement("RT", triangle, 1),
+                                  FiniteElement("DG", interval, 1))),
+        HDiv(TensorProductElement(FiniteElement("N1curl", triangle, 1),
+                                  FiniteElement("DG", interval, 1)))
     ]
     for hdiv_element in hdiv_elements:
         assert hdiv_element in HDiv
         assert hdiv_element in L2
+        assert hdiv_element in H0dx0dy
         assert hdiv_element not in H1
+        assert hdiv_element not in H1dx1dy
         assert hdiv_element not in HCurl
         assert hdiv_element not in H2
+        assert hdiv_element not in H2dx2dy
 
 
 def test_contains_hcurl():
     hcurl_elements = [
         FiniteElement("N1curl", triangle, 1),
         FiniteElement("N2curl", triangle, 1),
+        # HCurl elements:
+        HCurl(TensorProductElement(FiniteElement("CG", triangle, 1),
+                                   FiniteElement("DG", interval, 1))),
+        HCurl(TensorProductElement(FiniteElement("N1curl", triangle, 1),
+                                   FiniteElement("CG", interval, 1))),
+        HCurl(TensorProductElement(FiniteElement("RT", triangle, 1),
+                                   FiniteElement("CG", interval, 1)))
+    ]
+    for hcurl_element in hcurl_elements:
+        assert hcurl_element in HCurl
+        assert hcurl_element in L2
+        assert hcurl_element in H0dx0dy
+        assert hcurl_element not in H1
+        assert hcurl_element not in H1dx1dy
+        assert hcurl_element not in HDiv
+        assert hcurl_element not in H2
+        assert hcurl_element not in H2dx2dy
+
+
+def test_enriched_elements_hdiv():
+    A = FiniteElement("CG", interval, 1)
+    B = FiniteElement("DG", interval, 0)
+    AxB = TensorProductElement(A, B)
+    BxA = TensorProductElement(B, A)
+    C = FiniteElement("RTCF", quadrilateral, 1)
+    D = FiniteElement("DQ", quadrilateral, 0)
+    Q1 = TensorProductElement(C, B)
+    Q2 = TensorProductElement(D, A)
+    hdiv_elements = [
+        EnrichedElement(HDiv(AxB), HDiv(BxA)),
+        EnrichedElement(HDiv(Q1), HDiv(Q2))
+    ]
+    for hdiv_element in hdiv_elements:
+        assert hdiv_element in HDiv
+        assert hdiv_element in L2
+        assert hdiv_element in H0dx0dy
+        assert hdiv_element not in H1
+        assert hdiv_element not in H1dx1dy
+        assert hdiv_element not in HCurl
+        assert hdiv_element not in H2
+        assert hdiv_element not in H2dx2dy
+
+
+def test_enriched_elements_hcurl():
+    A = FiniteElement("CG", interval, 1)
+    B = FiniteElement("DG", interval, 0)
+    AxB = TensorProductElement(A, B)
+    BxA = TensorProductElement(B, A)
+    C = FiniteElement("RTCE", quadrilateral, 1)
+    D = FiniteElement("DQ", quadrilateral, 0)
+    Q1 = TensorProductElement(C, B)
+    Q2 = TensorProductElement(D, A)
+    hcurl_elements = [
+        EnrichedElement(HCurl(AxB), HCurl(BxA)),
+        EnrichedElement(HCurl(Q1), HCurl(Q2))
     ]
     for hcurl_element in hcurl_elements:
         assert hcurl_element in HCurl
         assert hcurl_element in L2
+        assert hcurl_element in H0dx0dy
         assert hcurl_element not in H1
+        assert hcurl_element not in H1dx1dy
         assert hcurl_element not in HDiv
         assert hcurl_element not in H2
+        assert hcurl_element not in H2dx2dy
+
+
+def test_varying_continuity_elements():
+    P1DG_t = FiniteElement("DG", triangle, 1)
+    P1DG_i = FiniteElement("DG", interval, 1)
+    P1 = FiniteElement("CG", interval, 1)
+    P2 = FiniteElement("CG", interval, 2)
+    P3 = FiniteElement("CG", interval, 3)
+    RT1 = FiniteElement("RT", triangle, 1)
+    ARG = FiniteElement("ARG", triangle, 1)
+
+    # Tensor product elements
+    P1DGP2 = TensorProductElement(P1DG_t, P2)
+    P1P1DG = TensorProductElement(P1, P1DG_i)
+    P1DGP1 = TensorProductElement(P1DG_i, P1)
+    RT1DG1 = TensorProductElement(RT1, P1DG_i)
+    P2P3 = TensorProductElement(P2, P3)
+    ARGP3 = TensorProductElement(ARG, P3)
+
+    assert P1DGP2 in H1dz and P1DGP2 in L2
+    assert P1DGP2 not in H1dh
+    assert P1DGP1 in H1dy and P1DGP2 in L2
+    assert P1P1DG in H1dx and P1P1DG in L2
+    assert P1P1DG not in H1dx1dy
+    assert RT1DG1 in H000 and RT1DG1 in L2
+    assert P2P3 in H1dx1dy and P2P3 in H1
+    assert ARG in H2dx2dy
+    assert ARGP3 in H2dhH1dz
diff --git a/test/test_tensoralgebra.py b/test/test_tensoralgebra.py
index a25a059..dc2b002 100755
--- a/test/test_tensoralgebra.py
+++ b/test/test_tensoralgebra.py
@@ -157,19 +157,21 @@ def test_tr(self, A):
     self.assertEqualValues(C, D)
 
 
-def xtest_det(self, A):
+def test_det(self, A):
+    dims = (0, 1)
     C = det(A)
-    D = zero()  # FIXME: Add expected value here
+    D = sum((-A[i, 0]*A[0, i] if i !=0 else A[i-1, -1]*A[i, 0]) for i in dims)
     self.assertEqualValues(C, D)
 
 
-def xtest_cofac(self, A):
+def test_cofac(self, A):
     C = cofac(A)
-    D = 0*C  # FIXME: Add expected value here
+    D = as_matrix([[(-A[i,j] if i != j else A[i,j]) for j in (-1,0)] for i in (-1,0)])
     self.assertEqualValues(C, D)
 
 
 def xtest_inv(self, A):
     C = inv(A)
-    D = 0*C  # FIXME: Add expected value here
+    detA = sum((-A[i, 0]*A[0, i] if i !=0 else A[i-1, -1]*A[i, 0]) for i in (0,1))
+    D = as_matrix([[(-A[i,j] if i != j else A[i,j]) for j in (-1,0)] for i in (-1,0)]) / detA  # FIXME: Test fails probably due to integer division
     self.assertEqualValues(C, D)
diff --git a/ufl/__init__.py b/ufl/__init__.py
index e474abc..674c14c 100644
--- a/ufl/__init__.py
+++ b/ufl/__init__.py
@@ -77,6 +77,7 @@ A very brief overview of the language contents follows:
     - VectorElement
     - TensorElement
     - EnrichedElement
+    - NodalEnrichedElement
     - RestrictedElement
     - TensorProductElement
     - HDivElement
@@ -241,7 +242,9 @@ A very brief overview of the language contents follows:
 # Modified by Lawrence Mitchell, 2014
 # Modified by Massimiliano Leoni, 2016
 
-__version__ = "2016.2.0"
+import pkg_resources
+
+__version__ = pkg_resources.get_distribution("ufl").version
 
 ########## README
 # Imports here should be what the user sees when doing "from ufl import *",
@@ -276,7 +279,7 @@ from ufl.sobolevspace import L2, H1, H2, HDiv, HCurl
 # Finite elements classes
 from ufl.finiteelement import FiniteElementBase, FiniteElement, \
     MixedElement, VectorElement, TensorElement, EnrichedElement, \
-    RestrictedElement, TensorProductElement, \
+    NodalEnrichedElement, RestrictedElement, TensorProductElement, \
     HDivElement, HCurlElement, BrokenElement, \
     FacetElement, InteriorElement
 
@@ -376,7 +379,7 @@ __all__ = as_native_strings([
     'Jacobian', 'JacobianDeterminant', 'JacobianInverse',
     'FiniteElementBase', 'FiniteElement',
     'MixedElement', 'VectorElement', 'TensorElement', 'EnrichedElement',
-    'RestrictedElement', 'TensorProductElement',
+    'NodalEnrichedElement', 'RestrictedElement', 'TensorProductElement',
     'HDivElement', 'HCurlElement',
     'BrokenElement', 'FacetElement', 'InteriorElement',
     'register_element', 'show_elements',
diff --git a/ufl/algorithms/compute_form_data.py b/ufl/algorithms/compute_form_data.py
index 3388bbf..f700bf7 100644
--- a/ufl/algorithms/compute_form_data.py
+++ b/ufl/algorithms/compute_form_data.py
@@ -30,7 +30,6 @@ from ufl.algorithms.analysis import extract_coefficients, extract_sub_elements,
 from ufl.algorithms.formdata import FormData
 from ufl.algorithms.formtransformations import compute_form_arities
 from ufl.algorithms.check_arities import check_form_arity
-from ufl.algorithms.elementtransformations import reconstruct_element
 
 # These are the main symbolic processing steps:
 from ufl.algorithms.apply_function_pullbacks import apply_function_pullbacks
@@ -103,9 +102,7 @@ def _compute_element_mapping(form):
 
         # Reconstruct element and add to map
         if reconstruct:
-            element_mapping[element] = reconstruct_element(element,
-                                                           element.family(),
-                                                           cell, degree)
+            element_mapping[element] = element.reconstruct(cell=cell, degree=degree)
         else:
             element_mapping[element] = element
 
diff --git a/ufl/algorithms/elementtransformations.py b/ufl/algorithms/elementtransformations.py
index 0c364c9..9e68c70 100644
--- a/ufl/algorithms/elementtransformations.py
+++ b/ufl/algorithms/elementtransformations.py
@@ -23,7 +23,7 @@
 from ufl.utils.py23 import as_native_strings
 from ufl.log import error
 from ufl.finiteelement import FiniteElement, VectorElement, TensorElement, \
-    MixedElement, EnrichedElement
+    MixedElement, EnrichedElement, NodalEnrichedElement
 
 __all__ = as_native_strings(['increase_order', 'tear'])
 
@@ -38,7 +38,7 @@ def change_regularity(element, family):
     For a given finite element, return the corresponding space
     specified by 'family'.
     """
-    return _change_family(element, family)
+    return element.reconstruct(family=family)
 
 
 def tear(element):
@@ -46,42 +46,18 @@ def tear(element):
     return change_regularity(element, "DG")
 
 
-def reconstruct_element(element, family, cell, degree):
-    if isinstance(element, FiniteElement):
-        return FiniteElement(family, cell, degree)
-    elif isinstance(element, VectorElement):
-        return VectorElement(family, cell, degree, dim=element.value_shape()[0])
-    elif isinstance(element, TensorElement):
-        return TensorElement(family, cell, degree, shape=element.value_shape())
-    else:
-        error("Element reconstruction is only done to stay compatible"
-              " with hacks in DOLFIN. Not expecting a %s" % repr(element))
-
-
 def _increase_degree(element, degree_rise):
     if isinstance(element, (FiniteElement, VectorElement, TensorElement)):
-        return reconstruct_element(element, element.family(), element.cell(),
-                                   element.degree() + degree_rise)
+        return element.reconstruct(degree=(element.degree() + degree_rise))
     elif isinstance(element, MixedElement):
         return MixedElement([_increase_degree(e, degree_rise)
                              for e in element.sub_elements()])
     elif isinstance(element, EnrichedElement):
         return EnrichedElement([_increase_degree(e, degree_rise)
                                 for e in element.sub_elements()])
-    else:
-        error("Element reconstruction is only done to stay compatible"
-              " with hacks in DOLFIN. Not expecting a %s" % repr(element))
-
-
-def _change_family(element, family):
-    if isinstance(element, (FiniteElement, VectorElement, TensorElement)):
-        return reconstruct_element(element, family, element.cell(), element.degree())
-    elif isinstance(element, MixedElement):
-        return MixedElement([_change_family(e, family)
-                             for e in element.sub_elements()])
-    elif isinstance(element, EnrichedElement):
-        return EnrichedElement([_change_family(e, family)
-                                for e in element.sub_elements()])
+    elif isinstance(element, NodalEnrichedElement):
+        return NodalEnrichedElement([_increase_degree(e, degree_rise)
+                                     for e in element.sub_elements()])
     else:
         error("Element reconstruction is only done to stay compatible"
               " with hacks in DOLFIN. Not expecting a %s" % repr(element))
diff --git a/ufl/constantvalue.py b/ufl/constantvalue.py
index e2b0e0e..da5f1fe 100644
--- a/ufl/constantvalue.py
+++ b/ufl/constantvalue.py
@@ -42,10 +42,10 @@ precision = None
 
 def format_float(x):
     "Format float value based on global UFL precision."
-    if precision is None:
-        return "%s" % repr(x)
+    if precision:
+        return "{:.{prec}}".format(float(x), prec=precision)
     else:
-        return ("%%.%dg" % precision) % x
+        return "{}".format(float(x))
 
 
 # --- Base classes for constant types ---
diff --git a/ufl/exproperators.py b/ufl/exproperators.py
index 77920ae..3a43561 100644
--- a/ufl/exproperators.py
+++ b/ufl/exproperators.py
@@ -103,6 +103,8 @@ def _as_tensor(self, indices):
     if not all(isinstance(i, Index) for i in indices):
         error("Expecting a tuple of Index objects to A^indices := as_tensor(A, indices).")
     return as_tensor(self, indices)
+
+
 Expr.__xor__ = _as_tensor
 
 
@@ -178,6 +180,7 @@ def _mult(a, b):
 
     return p
 
+
 # --- Extend Expr with algebraic operators ---
 
 _valid_types = (Expr, numbers.Real, numbers.Integral)
@@ -188,6 +191,8 @@ def _mul(self, o):
         return NotImplemented
     o = as_ufl(o)
     return _mult(self, o)
+
+
 Expr.__mul__ = _mul
 
 
@@ -196,6 +201,8 @@ def _rmul(self, o):
         return NotImplemented
     o = as_ufl(o)
     return _mult(o, self)
+
+
 Expr.__rmul__ = _rmul
 
 
@@ -203,6 +210,8 @@ def _add(self, o):
     if not isinstance(o, _valid_types):
         return NotImplemented
     return Sum(self, o)
+
+
 Expr.__add__ = _add
 
 
@@ -214,6 +223,8 @@ def _radd(self, o):
         # needed for sum([a,b])
         return self
     return Sum(o, self)
+
+
 Expr.__radd__ = _radd
 
 
@@ -221,6 +232,8 @@ def _sub(self, o):
     if not isinstance(o, _valid_types):
         return NotImplemented
     return Sum(self, -o)
+
+
 Expr.__sub__ = _sub
 
 
@@ -228,6 +241,8 @@ def _rsub(self, o):
     if not isinstance(o, _valid_types):
         return NotImplemented
     return Sum(o, -self)
+
+
 Expr.__rsub__ = _rsub
 
 
@@ -240,6 +255,8 @@ def _div(self, o):
         d = Division(self[ii], o)
         return as_tensor(d, ii)
     return Division(self, o)
+
+
 Expr.__div__ = _div
 Expr.__truediv__ = _div
 
@@ -248,6 +265,8 @@ def _rdiv(self, o):
     if not isinstance(o, _valid_types):
         return NotImplemented
     return Division(o, self)
+
+
 Expr.__rdiv__ = _rdiv
 Expr.__rtruediv__ = _rdiv
 
@@ -258,6 +277,8 @@ def _pow(self, o):
     if o == 2 and self.ufl_shape:
         return Inner(self, self)
     return Power(self, o)
+
+
 Expr.__pow__ = _pow
 
 
@@ -265,17 +286,23 @@ def _rpow(self, o):
     if not isinstance(o, _valid_types):
         return NotImplemented
     return Power(o, self)
+
+
 Expr.__rpow__ = _rpow
 
 
 # TODO: Add Negated class for this? Might simplify reductions in Add.
 def _neg(self):
     return -1*self
+
+
 Expr.__neg__ = _neg
 
 
 def _abs(self):
     return Abs(self)
+
+
 Expr.__abs__ = _abs
 
 
@@ -312,6 +339,8 @@ def _call(self, arg, mapping=None, component=()):
         return _restrict(self, arg)
     else:
         return _eval(self, arg, mapping, component)
+
+
 Expr.__call__ = _call
 
 
@@ -321,6 +350,8 @@ def _transpose(self):
     """Transpose a rank-2 tensor expression. For more general transpose
     operations of higher order tensor expressions, use indexing and Tensor."""
     return Transposed(self)
+
+
 Expr.T = property(_transpose)
 
 
@@ -461,6 +492,7 @@ def _getitem(self, component):
 
     return a
 
+
 Expr.__getitem__ = _getitem
 
 
@@ -479,4 +511,5 @@ def _dx(self, *ii):
     # Take all components, applying repeated index sums in the [] operation
     return d.__getitem__((Ellipsis,) + ii)
 
+
 Expr.dx = _dx
diff --git a/ufl/finiteelement/__init__.py b/ufl/finiteelement/__init__.py
index 3499046..2201bd9 100644
--- a/ufl/finiteelement/__init__.py
+++ b/ufl/finiteelement/__init__.py
@@ -30,6 +30,7 @@ from ufl.finiteelement.mixedelement import MixedElement
 from ufl.finiteelement.mixedelement import VectorElement
 from ufl.finiteelement.mixedelement import TensorElement
 from ufl.finiteelement.enrichedelement import EnrichedElement
+from ufl.finiteelement.enrichedelement import NodalEnrichedElement
 from ufl.finiteelement.restrictedelement import RestrictedElement
 from ufl.finiteelement.tensorproductelement import TensorProductElement
 from ufl.finiteelement.hdivcurl import HDivElement, HCurlElement
@@ -45,6 +46,7 @@ __all_classes__ = [
     "VectorElement",
     "TensorElement",
     "EnrichedElement",
+    "NodalEnrichedElement",
     "RestrictedElement",
     "TensorProductElement",
     "HDivElement",
diff --git a/ufl/finiteelement/brokenelement.py b/ufl/finiteelement/brokenelement.py
index 7bc2e92..8263fc2 100644
--- a/ufl/finiteelement/brokenelement.py
+++ b/ufl/finiteelement/brokenelement.py
@@ -42,6 +42,9 @@ class BrokenElement(FiniteElementBase):
     def mapping(self):
         return self._element.mapping()
 
+    def reconstruct(self, **kwargs):
+        return BrokenElement(self._element.reconstruct(**kwargs))
+
     def __str__(self):
         return "BrokenElement(%s)" % str(self._element)
 
diff --git a/ufl/finiteelement/elementlist.py b/ufl/finiteelement/elementlist.py
index 07cb7f2..e776041 100644
--- a/ufl/finiteelement/elementlist.py
+++ b/ufl/finiteelement/elementlist.py
@@ -83,6 +83,7 @@ def show_elements():
         print("Defined on cellnames: %s" % (cellnames,))
         print()
 
+
 # FIXME: Consider cleanup of element names. Use notation from periodic
 # table as the main, keep old names as compatibility aliases.
 
diff --git a/ufl/finiteelement/enrichedelement.py b/ufl/finiteelement/enrichedelement.py
index 6a581bf..cb702ce 100644
--- a/ufl/finiteelement/enrichedelement.py
+++ b/ufl/finiteelement/enrichedelement.py
@@ -30,8 +30,8 @@ from ufl.finiteelement.finiteelementbase import FiniteElementBase
 
 
 # @six.python_2_unicode_compatible
-class EnrichedElement(FiniteElementBase):
-    """The vector sum of two finite element spaces:
+class EnrichedElementBase(FiniteElementBase):
+    """The vector sum of several finite element spaces:
 
         .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}.
     """
@@ -68,23 +68,58 @@ class EnrichedElement(FiniteElementBase):
         # if not all(e.mapping() == mapping for e in elements[1:]):
         #    error("Element mapping mismatch.")
 
+        # Get name of subclass: EnrichedElement or NodalEnrichedElement
+        class_name = as_native_str(self.__class__.__name__)
+
         # Initialize element data
-        FiniteElementBase.__init__(self, "EnrichedElement", cell, degree,
+        FiniteElementBase.__init__(self, class_name, cell, degree,
                                    quad_scheme, value_shape,
                                    reference_value_shape)
 
         # Cache repr string
-        self._repr = as_native_str("EnrichedElement(%s)" %
-            ", ".join(repr(e) for e in self._elements))
+        self._repr = as_native_str("%s(%s)" %
+            (class_name, ", ".join(repr(e) for e in self._elements)))
+
+    def mapping(self):
+        return self._elements[0].mapping()
+
+    def sobolev_space(self):
+        "Return the underlying Sobolev space."
+        elements = [e for e in self._elements]
+        if all(e.sobolev_space() == elements[0].sobolev_space()
+               for e in elements):
+            return elements[0].sobolev_space()
+        else:
+            # Find smallest shared Sobolev space over all sub elements
+            spaces = [e.sobolev_space() for e in elements]
+            superspaces = [{s} | set(s.parents) for s in spaces]
+            intersect = set.intersection(*superspaces)
+            for s in intersect.copy():
+                for parent in s.parents:
+                    intersect.discard(parent)
+
+            sobolev_space, = intersect
+            return sobolev_space
+
+    def reconstruct(self, **kwargs):
+        return type(self)(*[e.reconstruct(**kwargs) for e in self._elements])
 
+
+class EnrichedElement(EnrichedElementBase):
+    """The vector sum of several finite element spaces:
+
+        .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}.
+
+        Dual basis is a concatenation of subelements dual bases;
+        primal basis is a concatenation of subelements primal bases;
+        resulting element is not nodal even when subelements are.
+        Structured basis may be exploited in form compilers.
+    """
     def is_cellwise_constant(self):
         """Return whether the basis functions of this
         element is spatially constant over each cell."""
         return all(e.is_cellwise_constant() for e in self._elements)
 
-    def mapping(self):
-        return self._elements[0].mapping()
-
     def __str__(self):
         "Format as string for pretty printing."
         return "<%s>" % " + ".join(str(e) for e in self._elements)
@@ -92,3 +127,26 @@ class EnrichedElement(FiniteElementBase):
     def shortstr(self):
         "Format as string for pretty printing."
         return "<%s>" % " + ".join(e.shortstr() for e in self._elements)
+
+
+class NodalEnrichedElement(EnrichedElementBase):
+    """The vector sum of several finite element spaces:
+
+        .. math:: \\textrm{EnrichedElement}(V, Q) = \\{v + q | v \\in V, q \\in Q\\}.
+
+        Primal basis is reorthogonalized to dual basis which is
+        a concatenation of subelements dual bases; resulting
+        element is nodal.
+    """
+    def is_cellwise_constant(self):
+        """Return whether the basis functions of this
+        element is spatially constant over each cell."""
+        return False
+
+    def __str__(self):
+        "Format as string for pretty printing."
+        return "<Nodal enriched element(%s)>" % ", ".join(str(e) for e in self._elements)
+
+    def shortstr(self):
+        "Format as string for pretty printing."
+        return "NodalEnriched(%s)" % ", ".join(e.shortstr() for e in self._elements)
diff --git a/ufl/finiteelement/facetelement.py b/ufl/finiteelement/facetelement.py
index 806917a..e611d76 100644
--- a/ufl/finiteelement/facetelement.py
+++ b/ufl/finiteelement/facetelement.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (C) 2014 Andrew T. T. McRae
+# Copyright (C) 2017 Miklós Homolya
 #
 # This file is part of UFL.
 #
@@ -15,37 +15,13 @@
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with UFL. If not, see <http://www.gnu.org/licenses/>.
-#
-# Modified by Massimiliano Leoni, 2016
-
-# import six
-from ufl.utils.py23 import as_native_str
-from ufl.finiteelement.finiteelementbase import FiniteElementBase
-
-
-# @six.python_2_unicode_compatible
-class FacetElement(FiniteElementBase):
-    """A version of an existing Finite Element space in which all dofs
-    associated with the interior have been discarded."""
-    def __init__(self, element):
-        self._element = element
-        self._repr = as_native_str("FacetElement(%s)" % repr(element))
-
-        family = "FacetElement"
-        cell = element.cell()
-        degree = element.degree()
-        quad_scheme = element.quadrature_scheme()
-        value_shape = element.value_shape()
-        reference_value_shape = element.reference_value_shape()
-        FiniteElementBase.__init__(self, family, cell, degree,
-                                   quad_scheme, value_shape, reference_value_shape)
 
-    def mapping(self):
-        return self._element.mapping()
+from ufl.finiteelement.restrictedelement import RestrictedElement
+from ufl.log import deprecate
 
-    def __str__(self):
-        return "FacetElement(%s)" % str(self._element)
 
-    def shortstr(self):
-        "Format as string for pretty printing."
-        return "FacetElement(%s)" % str(self._element.shortstr())
+def FacetElement(element):
+    """Constructs the restriction of a finite element to the facets of the
+    cell."""
+    deprecate('FacetElement(element) is deprecated, please use element["facet"] instead.')
+    return RestrictedElement(element, restriction_domain="facet")
diff --git a/ufl/finiteelement/finiteelement.py b/ufl/finiteelement/finiteelement.py
index e04c54a..6cbfb81 100644
--- a/ufl/finiteelement/finiteelement.py
+++ b/ufl/finiteelement/finiteelement.py
@@ -43,6 +43,7 @@ class FiniteElement(FiniteElementBase):
         "_short_name",
         "_sobolev_space",
         "_mapping",
+        "_variant",
         ))
 
     def __new__(cls,
@@ -50,29 +51,31 @@ class FiniteElement(FiniteElementBase):
                 cell=None,
                 degree=None,
                 form_degree=None,
-                quad_scheme=None):
+                quad_scheme=None,
+                variant=None):
         """Intercepts construction to expand CG, DG, RTCE and RTCF
         spaces on TensorProductCells."""
         if cell is not None:
             cell = as_cell(cell)
 
         if isinstance(cell, TensorProductCell):
-            family, short_name, degree, value_shape, reference_value_shape, sobolev_space, mapping = \
-                canonical_element_description(family, cell, degree, form_degree)
-
             # Delay import to avoid circular dependency at module load time
             from ufl.finiteelement.tensorproductelement import TensorProductElement
             from ufl.finiteelement.enrichedelement import EnrichedElement
             from ufl.finiteelement.hdivcurl import HDivElement as HDiv, HCurlElement as HCurl
 
+            family, short_name, degree, value_shape, reference_value_shape, sobolev_space, mapping = \
+                canonical_element_description(family, cell, degree, form_degree)
+
             if family in ["RTCF", "RTCE"]:
-                if cell._cells[0].cellname() != "interval":
+                cell_h, cell_v = cell.sub_cells()
+                if cell_h.cellname() != "interval":
                     error("%s is available on TensorProductCell(interval, interval) only." % family)
-                if cell._cells[1].cellname() != "interval":
+                if cell_v.cellname() != "interval":
                     error("%s is available on TensorProductCell(interval, interval) only." % family)
 
-                C_elt = FiniteElement("CG", "interval", degree, 0, quad_scheme)
-                D_elt = FiniteElement("DG", "interval", degree - 1, 1, quad_scheme)
+                C_elt = FiniteElement("CG", "interval", degree, variant=variant)
+                D_elt = FiniteElement("DG", "interval", degree - 1, variant=variant)
 
                 CxD_elt = TensorProductElement(C_elt, D_elt, cell=cell)
                 DxC_elt = TensorProductElement(D_elt, C_elt, cell=cell)
@@ -83,48 +86,48 @@ class FiniteElement(FiniteElementBase):
                     return EnrichedElement(HCurl(CxD_elt), HCurl(DxC_elt))
 
             elif family == "NCF":
-                if cell._cells[0].cellname() != "quadrilateral":
+                cell_h, cell_v = cell.sub_cells()
+                if cell_h.cellname() != "quadrilateral":
                     error("%s is available on TensorProductCell(quadrilateral, interval) only." % family)
-                if cell._cells[1].cellname() != "interval":
+                if cell_v.cellname() != "interval":
                     error("%s is available on TensorProductCell(quadrilateral, interval) only." % family)
 
-                Qc_elt = FiniteElement("RTCF", "quadrilateral", degree, 1, quad_scheme)
-                Qd_elt = FiniteElement("DQ", "quadrilateral", degree - 1, 2, quad_scheme)
+                Qc_elt = FiniteElement("RTCF", "quadrilateral", degree, variant=variant)
+                Qd_elt = FiniteElement("DQ", "quadrilateral", degree - 1, variant=variant)
 
-                Id_elt = FiniteElement("DG", "interval", degree - 1, 1, quad_scheme)
-                Ic_elt = FiniteElement("CG", "interval", degree, 0, quad_scheme)
+                Id_elt = FiniteElement("DG", "interval", degree - 1, variant=variant)
+                Ic_elt = FiniteElement("CG", "interval", degree, variant=variant)
 
                 return EnrichedElement(HDiv(TensorProductElement(Qc_elt, Id_elt, cell=cell)),
                                        HDiv(TensorProductElement(Qd_elt, Ic_elt, cell=cell)))
 
             elif family == "NCE":
-                if cell._cells[0].cellname() != "quadrilateral":
+                cell_h, cell_v = cell.sub_cells()
+                if cell_h.cellname() != "quadrilateral":
                     error("%s is available on TensorProductCell(quadrilateral, interval) only." % family)
-                if cell._cells[1].cellname() != "interval":
+                if cell_v.cellname() != "interval":
                     error("%s is available on TensorProductCell(quadrilateral, interval) only." % family)
 
-                Qc_elt = FiniteElement("Q", "quadrilateral", degree, 0, quad_scheme)
-                Qd_elt = FiniteElement("RTCE", "quadrilateral", degree, 1, quad_scheme)
+                Qc_elt = FiniteElement("Q", "quadrilateral", degree, variant=variant)
+                Qd_elt = FiniteElement("RTCE", "quadrilateral", degree, variant=variant)
 
-                Id_elt = FiniteElement("DG", "interval", degree - 1, 1, quad_scheme)
-                Ic_elt = FiniteElement("CG", "interval", degree, 0, quad_scheme)
+                Id_elt = FiniteElement("DG", "interval", degree - 1, variant=variant)
+                Ic_elt = FiniteElement("CG", "interval", degree, variant=variant)
 
                 return EnrichedElement(HCurl(TensorProductElement(Qc_elt, Id_elt, cell=cell)),
                                        HCurl(TensorProductElement(Qd_elt, Ic_elt, cell=cell)))
 
             elif family == "Q":
-                return TensorProductElement(FiniteElement("CG", cell._cells[0], degree, 0, quad_scheme),
-                                            FiniteElement("CG", cell._cells[1], degree, 0, quad_scheme),
+                return TensorProductElement(*[FiniteElement("CG", c, degree, variant=variant)
+                                              for c in cell.sub_cells()],
                                             cell=cell)
 
             elif family == "DQ":
-                family_A = "DG" if cell._cells[0].cellname() in simplices else "DQ"
-                family_B = "DG" if cell._cells[1].cellname() in simplices else "DQ"
-                elem_A = FiniteElement(family_A, cell._cells[0], degree,
-                                       cell._cells[0].topological_dimension(), quad_scheme)
-                elem_B = FiniteElement(family_B, cell._cells[1], degree,
-                                       cell._cells[1].topological_dimension(), quad_scheme)
-                return TensorProductElement(elem_A, elem_B, cell=cell)
+                def dq_family(cell):
+                    return "DG" if cell.cellname() in simplices else "DQ"
+                return TensorProductElement(*[FiniteElement(dq_family(c), c, degree, variant=variant)
+                                              for c in cell.sub_cells()],
+                                            cell=cell)
 
         return super(FiniteElement, cls).__new__(cls)
 
@@ -133,7 +136,8 @@ class FiniteElement(FiniteElementBase):
                  cell=None,
                  degree=None,
                  form_degree=None,
-                 quad_scheme=None):
+                 quad_scheme=None,
+                 variant=None):
         """Create finite element.
 
         *Arguments*
@@ -148,6 +152,8 @@ class FiniteElement(FiniteElementBase):
                viewed as k-form)
             quad_scheme
                The quadrature scheme (optional)
+            variant
+               Hint for the local basis function variant (optional)
         """
         # Note: Unfortunately, dolfin sometimes passes None for
         # cell. Until this is fixed, allow it:
@@ -161,6 +167,7 @@ class FiniteElement(FiniteElementBase):
         self._sobolev_space = sobolev_space
         self._mapping = mapping
         self._short_name = short_name
+        self._variant = variant
 
         # Finite elements on quadrilaterals have an IrreducibleInt as degree
         if cell is not None:
@@ -168,6 +175,10 @@ class FiniteElement(FiniteElementBase):
                 from ufl.algorithms.estimate_degrees import IrreducibleInt
                 degree = IrreducibleInt(degree)
 
+        # Type check variant
+        if variant is not None and not isinstance(variant, str):
+            raise ValueError("Illegal variant: must be string or None")
+
         # Initialize element data
         FiniteElementBase.__init__(self, family, cell, degree, quad_scheme,
                                    value_shape, reference_value_shape)
@@ -178,8 +189,13 @@ class FiniteElement(FiniteElementBase):
             quad_str = ""
         else:
             quad_str = ", quad_scheme=%s" % repr(qs)
-        self._repr = as_native_str("FiniteElement(%s, %s, %s%s)" % (
-            repr(self.family()), repr(self.cell()), repr(self.degree()), quad_str))
+        v = self.variant()
+        if v is None:
+            var_str = ""
+        else:
+            var_str = ", variant=%s" % repr(qs)
+        self._repr = as_native_str("FiniteElement(%s, %s, %s%s%s)" % (
+            repr(self.family()), repr(self.cell()), repr(self.degree()), quad_str, var_str))
         assert '"' not in self._repr
 
     def mapping(self):
@@ -189,17 +205,33 @@ class FiniteElement(FiniteElementBase):
         "Return the underlying Sobolev space."
         return self._sobolev_space
 
+    def variant(self):
+        return self._variant
+
+    def reconstruct(self, family=None, cell=None, degree=None):
+        """Construct a new FiniteElement object with some properties
+        replaced with new values."""
+        if family is None:
+            family = self.family()
+        if cell is None:
+            cell = self.cell()
+        if degree is None:
+            degree = self.degree()
+        return FiniteElement(family, cell, degree, quad_scheme=self.quadrature_scheme(), variant=self.variant())
+
     def __str__(self):
         "Format as string for pretty printing."
         qs = self.quadrature_scheme()
         qs = "" if qs is None else "(%s)" % qs
-        return "<%s%s%s on a %s>" % (self._short_name, istr(self.degree()),
-                                     qs, self.cell())
+        v = self.variant()
+        v = "" if v is None else "(%s)" % v
+        return "<%s%s%s%s on a %s>" % (self._short_name, istr(self.degree()),
+                                       qs, v, self.cell())
 
     def shortstr(self):
         "Format as string for pretty printing."
-        return "%s%s(%s)" % (self._short_name, istr(self.degree()),
-                             istr(self.quadrature_scheme()))
+        return "%s%s(%s,%s)" % (self._short_name, istr(self.degree()),
+                                istr(self.quadrature_scheme()), istr(self.variant()))
 
     def __getnewargs__(self):
         """Return the arguments which pickle needs to recreate the object."""
@@ -207,4 +239,5 @@ class FiniteElement(FiniteElementBase):
                 self.cell(),
                 self.degree(),
                 None,
-                self.quadrature_scheme())
+                self.quadrature_scheme(),
+                self.variant())
diff --git a/ufl/finiteelement/finiteelementbase.py b/ufl/finiteelement/finiteelementbase.py
index 1e30297..0b3141c 100644
--- a/ufl/finiteelement/finiteelementbase.py
+++ b/ufl/finiteelement/finiteelementbase.py
@@ -38,7 +38,6 @@ class FiniteElementBase(object):
         "_family",
         "_cell",
         "_degree",
-        "_form_degree",
         "_quad_scheme",
         "_value_shape",
         "_reference_value_shape",
diff --git a/ufl/finiteelement/hdivcurl.py b/ufl/finiteelement/hdivcurl.py
index 431e4b1..584f689 100644
--- a/ufl/finiteelement/hdivcurl.py
+++ b/ufl/finiteelement/hdivcurl.py
@@ -22,6 +22,7 @@
 from ufl.utils.py23 import as_native_str
 from ufl.utils.py23 import as_native_strings
 from ufl.finiteelement.finiteelementbase import FiniteElementBase
+from ufl.sobolevspace import HDiv, HCurl
 
 
 # @six.python_2_unicode_compatible
@@ -48,6 +49,13 @@ class HDivElement(FiniteElementBase):
     def mapping(self):
         return "contravariant Piola"
 
+    def sobolev_space(self):
+        "Return the underlying Sobolev space."
+        return HDiv
+
+    def reconstruct(self, **kwargs):
+        return HDivElement(self._element.reconstruct(**kwargs))
+
     def __str__(self):
         return "HDivElement(%s)" % str(self._element)
 
@@ -81,6 +89,13 @@ class HCurlElement(FiniteElementBase):
     def mapping(self):
         return "covariant Piola"
 
+    def sobolev_space(self):
+        "Return the underlying Sobolev space."
+        return HCurl
+
+    def reconstruct(self, **kwargs):
+        return HCurlElement(self._element.reconstruct(**kwargs))
+
     def __str__(self):
         return "HCurlElement(%s)" % str(self._element)
 
diff --git a/ufl/finiteelement/interiorelement.py b/ufl/finiteelement/interiorelement.py
index 30092bd..e1762d7 100644
--- a/ufl/finiteelement/interiorelement.py
+++ b/ufl/finiteelement/interiorelement.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright (C) 2014 Andrew T. T. McRae
+# Copyright (C) 2017 Miklós Homolya
 #
 # This file is part of UFL.
 #
@@ -15,37 +15,13 @@
 #
 # You should have received a copy of the GNU Lesser General Public License
 # along with UFL. If not, see <http://www.gnu.org/licenses/>.
-#
-# Modified by Massimiliano Leoni, 2016
-
-# import six
-from ufl.utils.py23 import as_native_str
-from ufl.finiteelement.finiteelementbase import FiniteElementBase
-
-
-# @six.python_2_unicode_compatible
-class InteriorElement(FiniteElementBase):
-    """A version of an existing Finite Element space in which only the dofs
-    associated with the interior have been kept."""
-    def __init__(self, element):
-        self._element = element
-        self._repr = as_native_str("InteriorElement(%s)" % repr(element))
-
-        family = "InteriorElement"
-        cell = element.cell()
-        degree = element.degree()
-        quad_scheme = element.quadrature_scheme()
-        value_shape = element.value_shape()
-        reference_value_shape = element.reference_value_shape()
-        FiniteElementBase.__init__(self, family, cell, degree,
-                                   quad_scheme, value_shape, reference_value_shape)
 
-    def mapping(self):
-        return self._element.mapping()
+from ufl.finiteelement.restrictedelement import RestrictedElement
+from ufl.log import deprecate
 
-    def __str__(self):
-        return "InteriorElement(%s)" % str(self._element)
 
-    def shortstr(self):
-        "Format as string for pretty printing."
-        return "InteriorElement(%s)" % str(self._element.shortstr())
+def InteriorElement(element):
+    """Constructs the restriction of a finite element to the interior of
+    the cell."""
+    deprecate('InteriorElement(element) is deprecated, please use element["interior"] instead.')
+    return RestrictedElement(element, restriction_domain="interior")
diff --git a/ufl/finiteelement/mixedelement.py b/ufl/finiteelement/mixedelement.py
index d706684..5a54f93 100644
--- a/ufl/finiteelement/mixedelement.py
+++ b/ufl/finiteelement/mixedelement.py
@@ -245,6 +245,9 @@ class MixedElement(FiniteElementBase):
             i, e = self.extract_component(component)
             return e.degree()
 
+    def reconstruct(self, **kwargs):
+        return MixedElement(*[e.reconstruct(**kwargs) for e in self.sub_elements()])
+
     def __str__(self):
         "Format as string for pretty printing."
         tmp = ", ".join(str(element) for element in self._sub_elements)
@@ -313,12 +316,15 @@ class VectorElement(MixedElement):
         self._family = sub_element.family()
         self._degree = sub_element.degree()
         self._sub_element = sub_element
-        self._form_degree = form_degree  # Storing for signature_data, not sure if it's needed
 
         # Cache repr string
         self._repr = "VectorElement(%s, dim=%d)" % (
             repr(sub_element), len(self._sub_elements))
 
+    def reconstruct(self, **kwargs):
+        sub_element = self._sub_element.reconstruct(**kwargs)
+        return VectorElement(sub_element, dim=len(self.sub_elements()))
+
     def __str__(self):
         "Format as string for pretty printing."
         return ("<vector element with %d components of %s>" %
@@ -470,6 +476,10 @@ class TensorElement(MixedElement):
         A component is a tuple of one or more ints."""
         return self._symmetry
 
+    def reconstruct(self, **kwargs):
+        sub_element = self._sub_element.reconstruct(**kwargs)
+        return TensorElement(sub_element, shape=self._shape, symmetry=self._symmetry)
+
     def __str__(self):
         "Format as string for pretty printing."
         if self._symmetry:
diff --git a/ufl/finiteelement/restrictedelement.py b/ufl/finiteelement/restrictedelement.py
index 18b4bd5..467f993 100644
--- a/ufl/finiteelement/restrictedelement.py
+++ b/ufl/finiteelement/restrictedelement.py
@@ -73,6 +73,10 @@ class RestrictedElement(FiniteElementBase):
         "Return the domain onto which the element is restricted."
         return self._restriction_domain
 
+    def reconstruct(self, **kwargs):
+        element = self._element.reconstruct(**kwargs)
+        return RestrictedElement(element, self._restriction_domain)
+
     def __str__(self):
         "Format as string for pretty printing."
         return "<%s>|_{%s}" % (self._element, self._restriction_domain)
diff --git a/ufl/finiteelement/tensorproductelement.py b/ufl/finiteelement/tensorproductelement.py
index 59ccff0..7a482e5 100644
--- a/ufl/finiteelement/tensorproductelement.py
+++ b/ufl/finiteelement/tensorproductelement.py
@@ -28,6 +28,7 @@ from itertools import chain
 from ufl.log import error
 from ufl.utils.py23 import as_native_strings
 from ufl.cell import TensorProductCell, as_cell
+from ufl.sobolevspace import DirectionalSobolevSpace
 
 from ufl.finiteelement.finiteelementbase import FiniteElementBase
 
@@ -90,6 +91,22 @@ class TensorProductElement(FiniteElementBase):
         else:
             return "undefined"
 
+    def sobolev_space(self):
+        "Return the underlying Sobolev space of the TensorProductElement."
+        elements = self._sub_elements
+        if all(e.sobolev_space() == elements[0].sobolev_space()
+               for e in elements):
+            return elements[0].sobolev_space()
+        else:
+            # Generate a DirectionalSobolevSpace which contains
+            # continuity information parametrized by spatial index
+            orders = []
+            for e in elements:
+                e_dim = e.cell().geometric_dimension()
+                e_order = (e.sobolev_space()._order,) * e_dim
+                orders.extend(e_order)
+            return DirectionalSobolevSpace(orders)
+
     def num_sub_elements(self):
         "Return number of subelements."
         return len(self._sub_elements)
@@ -98,6 +115,9 @@ class TensorProductElement(FiniteElementBase):
         "Return subelements (factors)."
         return self._sub_elements
 
+    def reconstruct(self, cell=None):
+        return TensorProductElement(*self.sub_elements(), cell=cell)
+
     def __str__(self):
         "Pretty-print."
         return "TensorProductElement(%s, cell=%s)" \
diff --git a/ufl/functionspace.py b/ufl/functionspace.py
index 94c4816..714847c 100644
--- a/ufl/functionspace.py
+++ b/ufl/functionspace.py
@@ -44,6 +44,19 @@ class AbstractFunctionSpace(object):
 @attach_operators_from_hash_data
 class FunctionSpace(AbstractFunctionSpace):
     def __init__(self, domain, element):
+        if domain is None:
+            # DOLFIN hack
+            # TODO: Is anything expected from element.cell() in this case?
+            pass
+        else:
+            try:
+                domain_cell = domain.ufl_cell()
+            except AttributeError:
+                error("Expected non-abstract domain for initalization of function space.")
+            else:
+                if element.cell() != domain_cell:
+                    error("Non-matching cell of finite element and domain.")
+
         AbstractFunctionSpace.__init__(self)
         self._ufl_domain = domain
         self._ufl_element = element
diff --git a/ufl/log.py b/ufl/log.py
index 9430b72..e398a51 100644
--- a/ufl/log.py
+++ b/ufl/log.py
@@ -54,6 +54,7 @@ def emit(self, record):
     self.stream.write(format_string % message)
     self.flush()
 
+
 # Colors if the terminal supports it (disabled e.g. when piped to
 # file)
 if sys.stdout.isatty() and sys.stderr.isatty():
@@ -218,8 +219,7 @@ class Logger:
         self._log.removeHandler(self._handler)
         self._log.addHandler(handler)
         self._handler = handler
-        handler.emit = types.MethodType(emit, self._handler,
-                                        self._handler.__class__)
+        handler.emit = types.MethodType(emit, self._handler)
 
     def get_logger(self):
         "Return message logger."
diff --git a/ufl/operators.py b/ufl/operators.py
index 27ab33a..e58eeff 100644
--- a/ufl/operators.py
+++ b/ufl/operators.py
@@ -417,6 +417,8 @@ def curl(f):
     "UFL operator: Take the curl of *f*."
     f = as_ufl(f)
     return Curl(f)
+
+
 rot = curl
 
 
diff --git a/ufl/sobolevspace.py b/ufl/sobolevspace.py
index 1360b55..f0814d8 100644
--- a/ufl/sobolevspace.py
+++ b/ufl/sobolevspace.py
@@ -23,12 +23,15 @@ symbolic reasoning about the spaces in which finite elements lie."""
 #
 # Modified by Martin Alnaes 2014
 # Modified by Lizao Li 2015
+# Modified by Thomas Gibson 2017
 
 #import six
 from ufl.utils.py23 import as_native_str
+from functools import total_ordering
 
 
 # @six.python_2_unicode_compatible
+ at total_ordering
 class SobolevSpace(object):
     """Symbolic representation of a Sobolev space. This implements a
     subset of the methods of a Python set so that finite elements and
@@ -46,6 +49,16 @@ class SobolevSpace(object):
         p = frozenset(parents or [])
         # Ensure that the inclusion operations are transitive.
         self.parents = p.union(*[p_.parents for p_ in p])
+        self._order = {"L2": 0,
+                       "H1": 1,
+                       "H2": 2,
+                       # Order for the elements below is taken from
+                       # its parent Sobolev space
+                       "HDiv": 0,
+                       "HCurl": 0,
+                       "HEin": 0,
+                       "HDivDiv": 0,
+                       "DirectionalH": 0}[self.name]
 
     def __unicode__(self):
         # Only in python 2
@@ -73,6 +86,12 @@ class SobolevSpace(object):
     def __hash__(self):
         return hash(("SobolevSpace", self.name))
 
+    def __getitem__(self, spatial_index):
+        """Returns the Sobolev space associated with a particular
+        spatial coordinate.
+        """
+        return self
+
     def __contains__(self, other):
         """Implement `fe in s` where `fe` is a
         :class:`~finiteelement.FiniteElement` and `s` is a
@@ -86,24 +105,9 @@ class SobolevSpace(object):
 
     def __lt__(self, other):
         """In common with intrinsic Python sets, < indicates "is a proper
-        subset of."""
+        subset of"."""
         return other in self.parents
 
-    def __le__(self, other):
-        """In common with intrinsic Python sets, <= indicates "is a subset
-        of." """
-        return (self == other) or (other in self.parents)
-
-    def __gt__(self, other):
-        """In common with intrinsic Python sets, > indicates "is a proper
-        superset of."""
-        return self in other.parents
-
-    def __ge__(self, other):
-        """In common with intrinsic Python sets, >= indicates "is a superset
-        of." """
-        return (self == other) or (self in other.parents)
-
     def __call__(self, element):
         """Syntax shortcut to create a HDivElement or HCurlElement."""
         if self.name == "HDiv":
@@ -115,10 +119,90 @@ class SobolevSpace(object):
         raise NotImplementedError("SobolevSpace has no call operator (only the specific HDiv and HCurl instances).")
 
 
+ at total_ordering
+class DirectionalSobolevSpace(SobolevSpace):
+    """Symbolic representation of a Sobolev space with varying smoothness
+    in differerent spatial directions.
+    """
+
+    def __init__(self, orders):
+        """Instantiate a DirectionalSobolevSpace object.
+
+        :arg orders: an iterable of orders of weak derivatives, where
+                     the position denotes in what spatial variable the
+                     smoothness requirement is enforced.
+        """
+        assert all(isinstance(x, int) for x in orders), (
+            "Order must be an integer."
+        )
+        assert all(x < 3 for x in orders), (
+            "Not implemented for orders greater than 2"
+        )
+        name = "DirectionalH"
+        parents = [L2]
+        super(DirectionalSobolevSpace, self).__init__(name, parents)
+        self._orders = tuple(orders)
+        self._spatial_indices = range(len(self._orders))
+
+    def __getitem__(self, spatial_index):
+        """Returns the Sobolev space associated with a particular
+        spatial coordinate.
+        """
+        if spatial_index not in range(len(self._orders)):
+            raise IndexError("Spatial index out of range.")
+        spaces = {0: L2,
+                  1: H1,
+                  2: H2}
+        return spaces[self._orders[spatial_index]]
+
+    def __contains__(self, other):
+        """Implement `fe in s` where `fe` is a
+        :class:`~finiteelement.FiniteElement` and `s` is a
+        :class:`DirectionalSobolevSpace`"""
+        if isinstance(other, SobolevSpace):
+            raise TypeError("Unable to test for inclusion of a " +
+                            "SobolevSpace in another SobolevSpace. " +
+                            "Did you mean to use <= instead?")
+        return (other.sobolev_space() == self or
+                all(self[i] in other.sobolev_space().parents
+                    for i in self._spatial_indices))
+
+    def __eq__(self, other):
+        if isinstance(other, DirectionalSobolevSpace):
+            return self._orders == other._orders
+        return all(self[i] == other for i in self._spatial_indices)
+
+    def __lt__(self, other):
+        """In common with intrinsic Python sets, < indicates "is a proper
+        subset of."""
+        if isinstance(other, DirectionalSobolevSpace):
+            if self._spatial_indices != other._spatial_indices:
+                return False
+            return any(self._orders[i] > other._orders[i]
+                       for i in self._spatial_indices)
+
+        if other in [HDiv, HCurl]:
+            return all(self._orders[i] >= 1 for i in self._spatial_indices)
+        elif other.name in ["HDivDiv", "HEin"]:
+            # Don't know how these spaces compare
+            return NotImplementedError(
+                "Don't know how to compare with %s" % other.name
+            )
+        else:
+            return any(self._orders[i] > other._order
+                       for i in self._spatial_indices)
+
+    def __str__(self):
+        return self.name + "(%s)" % ", ".join(map(str, self._orders))
+
+    def _repr_latex_(self):
+        return "H(%s)" % ", ".join(map(str, self._orders))
+
+
 L2 = SobolevSpace("L2")
 HDiv = SobolevSpace("HDiv", [L2])
 HCurl = SobolevSpace("HCurl", [L2])
 H1 = SobolevSpace("H1", [HDiv, HCurl, L2])
-H2 = SobolevSpace("H2", [H1])
+H2 = SobolevSpace("H2", [H1, HDiv, HCurl, L2])
 HEin = SobolevSpace("HEin", [L2])
 HDivDiv = SobolevSpace("HDivDiv", [L2])
diff --git a/ufl/utils/sorting.py b/ufl/utils/sorting.py
index 044ab93..6c1dc62 100644
--- a/ufl/utils/sorting.py
+++ b/ufl/utils/sorting.py
@@ -110,7 +110,7 @@ def canonicalize_metadata(metadata):
     for value in values:
         if isinstance(value, (dict, list, tuple)):
             value = canonicalize_metadata(value)
-        elif isinstance(value, (int, float, string_types)):
+        elif isinstance(value, (int, float, string_types)) or value is None:
             value = str(value)
         else:
             warning("Applying str() to a metadata value of type {0}, don't know if this is safe.".format(type(value).__name__))

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/fenics/ufl.git



More information about the debian-science-commits mailing list